java内存区域概述
扫描二维码
随时随地手机看文章
java内存区域
java虚拟机所管理的内存会包括以下几个运行时数据区:
程序计数器:存储下一条指令的地址,每个线程都有一个程序计数器
虚拟机栈:也是线程私有的,每进入一个方法都会在栈中申请一个栈帧用于存储局部变量,参数等。
本地方法栈:为native方法服务
堆:所有的线程共享,是存储实例对象的空间,可通过-Xmx和-Xms来指定堆的大小
方法区:存储已经加载的类信息,运行时常量池是方法区的一部分,用于存储直接引用和class文件中的常量池中的常量
对象访问
JVM主流的访问方式有两种:
句柄访问方式:栈中的引用指向堆中的句柄池,句柄池中有两项数据:方法区中的类数据和堆中的实例对象数据。
直接访问方式:栈中的引用直接指向堆中的一个数据结构,它包含实例对象数据以及到类数据的引用。
垃圾回收
找到垃圾的算法有如下这些:
引用计数算法:给对象添加一个引用计数器,每当有一个地方引用对象,就给对象的计数器加1,当引用失效时,计数器减1。当计数器为0时就是对象不再被使用了。这个算法无法解决对象之间互相引用的问题。
根搜索算法:从GC Roots作为起点,向下搜索,不可达的对象说明可以被回收。GC Roots可以是栈中的引用,类静态变量,常量的引用等。
对象的回收:一个对象即使不可达也没有立即死亡,垃圾回收器会调用一次它的finalize(),有可能它在那里把自己给拯救了,也有可能直接死了。如果重新变成可达,之后再被清理时就不会再调用finalize()了,也就是这个方法只被调用一次。
类的回收:一个类在以下情况下被视为无用的类:该类的所有实例已经被回收,加载该类的ClassLoader被回收,代表该类的Class对象已经不可达。
垃圾收集算法:
标记-清理算法:找到垃圾(使用根搜索算法)并标记他们,然后清理它们所在的内存。缺点是效率低,并且造成内存碎片。
复制算法:将内存对半分,只使用其中一块内存,每次收集都是复制有用对象到另一块没有用的内存中。缺点是浪费一半的空间。
标记整理算法:标记垃圾,然后把有用的对象整理到一端,并清理掉除此之外的内存。
分代收集算法:根据对象的生命周期(有的生命周期长,有的生命周期短)分为新生代和老年代。在新生代采用复制算法(大部分的新生代都会被回收,所以需要复制的对象就很少了),在老年代采用标记-整理算法或标记清理算法。
class文件格式
整个class文件本质上就是一张表,下面是其内容:
魔数(1-4字节):文件以COFEBABE开头,标识class文件,不是以它开头的都不会被虚拟机处理。
版本号(5-6,7-8字节):次版本号,主版本号。生成class文件的编译器的版本。可向前兼容,不可向后兼容。
常量池(9-n字节):开始的两个字节表示常量池中常量的数目,计数从1开始。接下来是每个常量,常量池中存放两大类的常量:字面量和符号引用。符号引用又分为三类:类和接口的权限定名,字段的名称和描述符,方法的名称和描述符。常量的项目类型有11种:
类型 标志 描述 结构
CONSTANT_Utf8_info 1UTF-8编码的字符串 tag(u1)+length(u2)+bytes(u1,length个)
CONSTANT_Integer_info 3整型字面量
tag(u1)+byte(u4)
CONSTANT_Float_info 4浮点型字面量 tag(u1)+byte(u4)
CONSTANT_Long_info 5长整型字面量 tag(u1)+byte(u8)
CONSTANT_Double_info 6双精度浮点型字面量 tag(u1)+byte(u8)
CONSTANT_Class_info 7类或接口的符号引用 tag(u1)+name_index(u2)
CONSTANT_String_info 8字符串类型字面量 tag(u1)+index(u2)
CONSTANT_Fieldref_info 9字段的符号引用 tag(u1)+index(u2)+index(u2)
CONSTANT_Methodref_info 10方法的符号引用 tag(u1)+index(u2)+index(u2)
CONSTANT_InterfaceMethodref_info 11接口方法的符号引用 tag(u1)+index(u2)+index(u2)
CONSTANT_NameAndType_info 12字段或方法的部分符号引用 tag(u1)+index(u2)+index(u2)
访问标志(n+1,n+2):16位来表示类是否为public等访问信息。
标志名称 值
ACC_PUBLIC 0x0001
ACC_FINAL 0x0010
ACC_SUPER 0x0020
ACC_INTERFACE 0x0200
ACC_ABSTRACT 0x0400
ACC_SYNTHETIC 0x1000
ACC_ANNOTATION 0x2000
ACC_ENUM 0x4000
类索引(n+3,n+4),父类索引(n+5,n+6):常量池中的某项的索引
接口索引集合(n+7,n+8):接口数量,以及接下来的每个接口在常量池中的索引
接下来还有一些可选的项,不细说了。
类的加载
类加载器机制:JVM把class文件加载到内存,然后验证,检验,解析,初始化,最终变成JVM可以使用的java类型。类型的加载和连接都是在程序的运行期间完成的。
类型的生命周期:加载,连接(验证,准备,解析),初始化,使用,卸载,其中解析过程可以延迟。
以下四种情况会对类进行初始化:
new,读写静态变量(不是final类型),调用静态方法
反射调用
如果父类还没初始化,就先初始化父类
如果是主类(运行main所在的类),则初始化主类
需要注意的是,当A[] a = new A[10]时并不会加载A类。
类加载的过程:加载,连接,初始化统称为类加载过程。
加载:加载阶段JVM完成三件事:根据类的全限定名加载二进制流(class文件),将class文件的静态存储结构转为方法区的运行时数据结构,创建一个代表该类的Class对象。
验证:确保class文件的字节流包含的信息符合当前虚拟机的要求。大致上完成四个检验过程:文件格式验证,元数据验证,字节流验证,符号引用验证。
准备:在方法区中为类变量分配内存,并赋初始值(0或null),如果是常量就直接为定义的值。
解析:将符号引用转换为直接引用,符号引用可以是可定位到目标的任何形式的字面量,可能还不在内存中;而直接引用则是已经在内存中的地址。解析主要是针对类,接口,字段,类或接口的方法这些符号引用进行的。
初始化:初始化的过程就是执行类构造器
类加载器
虚拟机的设计团队把类加载阶段中的“通过一个类的全限定名来获取描述这个类的二进制流”这个动作放到虚拟机外面去实现,以便让程序自己觉得如何获取所需要的类。这个动作的代码模块就是类加载器。
虽然类加载器只是加载类文件,但它的作用远远不限于类加载阶段。对于任意一个类,都需要由类加载器和类本身共同确定在虚拟机中的唯一性。
从虚拟机的角度看,类加载器分为两种,一是启动类加载器,它是虚拟机的一部分,由C++实现;二是其他的类加载器,独立于虚拟机,由java实现,继承ClassLoader抽象类
。
从java开发人员的角度来看,类加载器可再细分,通常会用到三种系统提供的类加载器:
启动类加载器:加载
扩展类加载器:加载
应用程序类加载器(也叫系统类加载器):加载用户classpath指定的目录下的所有类库,如果没有自定义那就使用默认的系统类加载器。
双亲委派模型:要求除了启动类加载器之外的所有类加载器都有自己的父类加载器,这里子类加载器并不是通过继承而是通过组合的方式来复用父类加载器的代码。双亲委派模型的工作流程是:当类加载器要加载一个类时总是先让父类加载器去加载,如此层层往上委派,最后到启动类加载器,如果它能加载就加载那个类,不能加载就层层往下让其子类加载器加载。
执行引擎
执行引擎是虚拟机的一个核心组成部分。虚拟机是相对于物理机而言的,区别在于物理机的执行引擎是建立在硬件,指令,操作系统的基础之上,而虚拟机的执行引擎则是由自己实现。因此虚拟机可以定义自己的指令集和执行引擎的体系结构。
运行时栈帧结构:栈帧结构是支持虚拟机方法调用的数据结构。它是虚拟机栈的栈元素,用于存储局部变量表(包括方法参数和方法内部定义的局部变量)、操作数栈、动态链接和方法返回地址等信息。每一个方法调用从开始到结束的过程也就是栈帧入栈和出栈的过程。