Java虚拟机内存详解
一、运行时数据区域
Java虚拟机在执行程序的过程会把它所管理的内存分为若干个不同的区域,这些区域都有各自的用途,以及创建和销毁时间,根据Java虚拟机规范,Java虚拟机所管理的内存分为以下几个运行时数据区域:
- 程序计数器
- Java虚拟机栈
- 本地方法栈
- Java堆
- 方法区
- 运行时常量池
- 直接内存
1.1 程序计数器
程序计数器是一块较小的内存空间,它是JVM内部虚拟寄存器,字节码解释器通过改变这个计数器的值来选取吓一条需要执行的字节码指令。程序计数器是线程私有的。
1.2 Java虚拟机栈
Java虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的时候,都会创建栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用到执行完的过程,就对应着一个栈帧在虚拟机栈中入栈到出站的过程,Java虚拟机栈同样也是线程私有的。
- 局部变量表:局部变量表是一组变量存储空间,它存放了编译器可知的各种基本数据类型、引用对象和returnAddress类型,局部变量表所需的内部空间在编译期间完成分配,当进入一个方法时这个方法需要在栈帧中分配多大的局部变量空间是完全确定的。
1.3 本地方法栈
本地方法栈与虚拟机栈非常相似,他们的区别是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用到native方法服务。
1.4 Java堆
Java堆是虚拟机管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此区域唯一的目的是用来存放对象实例
1.5 方法区
方法区和Java堆一样也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
1.6 运行时常量
运行时常量是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外还有一项信息是常量池
,用于存放编译期生成的各种字面量和符号引用。
1.7 直接内存
在JDK1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用native库函数直接分配堆外内存。
二、对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:
- 对象头
- 实例数据
- 对齐填充
2.1 对象头
对象头包括两部分信息,第一部分,用于存储自身对象的运行时数据,如哈希吗、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例
2.2 实例数据
实例数据部分是对象真正存储的有效信息
2.3 对齐填充
对齐填充并不必然存在,也没用特别的含义,它仅仅起着占位符的作用
三、对象的访问定位
对象创建之后保存在Java堆中,要想使用对象,需要通过栈上的reference数据在操作 。虚拟机规范只是规定了一个指向对象的具体位置,并没有定义这个引用通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机的实现。
目前有两种主流的访问方式,使用句柄和直接指针
- 使用句柄:JVM在Java堆中划分一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据和类型数据各自的具体地址信息。
- 直接指针:在reference堆栈中存放的是对象在Java堆中的地址,在堆中还需要存放
对象类型数据
的地址。
这2中方式各有优势,使用句柄来访问最大的好处是存储的是稳定的句柄地址,在对象移动时只会改变句柄池
中的实例数据指针,而reference本身不需要修;使用指针访问最大的好处是速度更快,它节省了一次指针定位的时间开销
对象类型数据和对象实例数据的理解:
- 对象实例数据指的是某个类new出来的具体实例
- 对象类型数据指的是Class类信息,定义了一个类的元数据、它包含的成员等