Java虚拟机运行时数据区
程序计数器(线程私有)
执行字节码的行号指示器
虚拟机栈(线程私有)
Java方法执行的本地线程模型:每个方法被执行的时候,Java虚拟机同步创建一个栈帧,里面存放局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从被调用到执行完毕,对应着一个栈桢在虚拟机栈中出栈到入栈的过程。很多人将JVM数据区笼统划分为堆内存和栈内存,栈内存通常就是指虚拟机栈,或者虚拟机栈中局部变量表部分。
局部变量表里存放两种类型数据,一种是基本类型,一种是引用类型。基本类型有三种,数值类型,布尔类型和returnAddress类型,数值类型和引用类型构成了8大基本数据类型,returnAddress类型指向一条字节码指令的地址,这是干什么用的呢?对于JVM来说,程序是存储在方法区的字节码指令,Java中每个线程私有一个程序计数器,程序计数器的值就是当前指令的地址,该值的类型就是returnAddress
本地方法栈(线程私有)
和虚拟机栈类似,差别是本地方法栈执行本地方法,虚拟机栈执行Java方法
堆(线程共有)
数据区中最大的一部分,JVM启动的时候被创建,用于存放几乎所有对象实例。这也是GC的区域,所以也叫GC堆
方法区(线程共有)
用于存储已被虚拟机加载的类型信息(类的完整名称、类的直接父类的完整名称、类的直接实现接口的有序列表、类型标志(类类型还是接口类型)类的修饰符)、常量池、静态变量、即时编译器编译后的代码缓存等数据,在JDK8之前,很多人将方法区称为永久代,这是因为hotspot虚拟机用永久代实现了方法区,因为永久代经常会溢出,到了jdk8之后,取消了永久代的概念,将方法区移到了元空间中,将字符串常量移到了堆内存中
运行时常量池
class文件中除了有类的版本、字段、方法、接口等信息之外,还有一个常量池表,用于存放编译器生成的字面量和符号引用,这个常量池表在类加载后存放到方法区的运行时常量池中。
- 字面量:
1 | int a = 1; // 1是字面量 |
- 符号引用:和直接引用相对,在代码编译阶段,编译器并不知道所引用的类的地址,就会用一个符号来代替,在JVM解析class阶段,会将符号引用解析为直接引用(指针/偏移量/句柄)
hotspot虚拟机与对象
虚拟机中对象的创建
- Java虚拟机遇到一条字节码new的指令时,先检查这个指令的参数能否在运行时常量池中找到对应的符号引用,并检查这个符号引用有没有被解析加载,如果没有,必须要先进行解析加载操作
- 虚拟机在堆内存中为该对象实例划分内存空间,这里有两种,一种是连续内存,连续内存里一段是已经使用的内存,一段是空闲内存,之间由一个指针指向分界点,这种划分内存就是让这个指针向空闲内存偏移一段地址,称为指针碰撞,另一种是非连续的内存,这种就需要维护一个list,记录哪段内存被占用了哪段没有,这种分配方法称为空闲列表。在这一个阶段中,假如是并发状态,分配空间的操作不一定是线程安全的,这时也有两种方案,一种是CAS加锁,另一种是让每个线程在虚拟机中预先分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)
- 内存分配完成之后,将这块内存空间都初始化为零值,这就保证了对象实例在Java代码中不赋初始值就能直接使用
- 对对象进行必要的设置,这些设置信息存放在对象头中
虚拟机中对象的布局(hotspot为例)
在JVM中,一个对象在堆内存中的存储布局由三部分组成,对象头,实例数据,对齐填充
- 对象头:对象头中存储两个方面的数据,运行时数据和类型指针。运行时数据主要有,GC分代年龄,锁信息,hashcode等,在32位系统中,运行时数据占32个比特,在64位系统中,运行时数据占64个比特,类型指针是一个指向对象的元类型数据的指针(也就是指向存在方法区的类型数据),虚拟机通过这个判断该对象是哪个类的实例。
- 实例数据:就是对象中存储的有效信息,也就是从父类继承的字段和自己定义的字段信息,这些字段会被按序分配内存
- 对齐填充:为了提高垃圾回收时指针扫描的效率,hotspot自动内存管理系统要求对象起始指针必须是8字节的整数倍,对象头占内存要么是8字节的一倍(32比特),要么是8字节两倍(64比特),如果实例数据不满足内存长度的要求,就会有对齐填充,起到对齐数据的作用
虚拟机中对象的访问定位
主要有两种方式:句柄定位,直接指针定位
- 句柄定位:上文我们提到,在虚拟机虚拟机栈的局部变量表中,有一个“reference”引用类型,如果是句柄定位的话,reference类型就指向堆内存中句柄池里的一个句柄,这个句柄包含两方面信息(指针),对象实例信息,对象类型信息,一个指针指向堆内存中实例池里的对象实例,一个指针指向方法区的对象类型数据。这个的优点是,如果对象实例被移动(比如垃圾回收时),只会改变句柄中的对应地址,而reference地址是稳定的
- 直接指针定位(hotspot主要使用这个方法):reference存储的就是堆内存里对象的地址,这样只需要一步就能直接访问到对象的实例数据,由上文对象的布局我们也可以知道,对象头里有一个类型指针,这个类型指针也会和句柄定位一样,指向方法区的类型数据