一、JVM运行时数据区
JVM运行时数据区如图1-1所示:
程序计数器:记录当前线程所执行字节码的行号指示器。
字节码解释器工作时通过该计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖该计数器完成。
Java虚拟机栈:描述Java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型、对象引用、returnAddress类型。long和double(64位)占用2个局部变量空间,其余数据类型占用1个。
栈帧是方法运行时的基础数据结构。
本地方法栈:与Java虚拟机栈类似,虚拟机栈为虚拟机执行Java方法(字节码)服务,本地方法栈为虚拟机使用Native方法服务。本地方法栈可自由实现。
Java堆:Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此区域用于存放对象实例,几乎所有的对象实例都在Java堆分配内存。Java堆也是垃圾收集器管理的主要区域。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区的实现不受约束。运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这些内容将在类加载进入方法区的运行时常量池中存放,具备动态性。
二、对象分配、布局和访问
Java对象创建的流程如下所示:
在虚拟机中,对象在内存中存储布局分为:1)对象头 2)实例数据 3)对齐填充。
对象头第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。Mark Word信息如下所示:
对象头的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。
对齐填充起着占位符的作用,8字节的整数倍。
对象访问
Java程序通过栈上的reference数据操作堆上的具体对象。访问方式分为句柄和直接指针两种。使用句柄访问,reference存储对象的句柄地址。
使用直接指针访问,reference中存储的对象地址。
使用句柄访问:reference中存储稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference无需修改。使用直接指针访问:速度更快,节省了一次指针定位的时间开销。