深入理解java虚拟机读书笔记 —— 第二章

117 阅读6分钟

2.1 运行时数据区域

image.png

2.1.1 程序计数器

与其他语言中函数执行的程序计数器类似,可以看作是当前线程所执行的字节码的行号指示符,用于存储下一条要执行的字节码指令的地址。
每个线程都需要一个独立的程序计数器来记录当前方法执行的位置,因此是线程私有的。在本地方法中,程序计数器的值应为空。

2.1.2 Java虚拟机栈

同样,java虚拟机栈也类似,也是线程私有的,用于存储每个方法的局部变量表,操作数栈,动态连接,方法出口等信息。每一个方法被调用至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。
局部变量表中的存储空间以局部变量槽来表示,long 和 double 类型的数据会占用两个变量槽,其余的类型只占用一个(与堆中类型所占的大小不同),所需内存空间在编译期间分配完成。

2.1.3 本地方法栈

与虚拟机栈类似,只是服务于本地方法。

2.1.4 Java堆

堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此区域的唯一目的是存放对象实例,因此也是垃圾收集器管理的内存区域。
java 堆可以处于物理上不连续的内存空间中,但在逻辑上应该视为连续的(如文件系统)。
java 堆可以是固定大小的,也可以是可扩展的(主流)。
从分配内存的角度看,堆可以划分出多个线程私有分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。

2.1.5 方法区

方法区也是线程共享的内存区域,用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据。
值得一提的是,方法区和永久代并不是等价的概念,在JDK8以前,HotSpot 虚拟机选择使用永久代来实现方法区,使得垃圾收集器能够像管理堆一样管理这部分内存,然而,这并不是一个好主意,永久代有内存上限,因而更容易遇到内存溢出的问题,因此,在JDK7中,讲原本放在永久代的字符串常量池,静态变量等移出永久代,到了JDK8中,完全废弃了永久代的概念,改用在本地内存中实现的元空间来代替。

2.1.6 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中的常量池表在类加载后被放到运行时常量池中,用于存放编译期间生成的各种字面量和符号引用
运行时常量池具备动态性,可以在运行期间将新的常量放入池中,如 String 类的 intern() 方法。

2.1.7 直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分。主要是因为在 JDK1.4 中引入了 NIO ,它可以使用 Native 函数库直接分配堆外内存,然后用存储在堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。

2.2 虚拟机对象探秘

2.2.1 对象的创建

类加载检查————分配内存————初始化为0————设置对象头————按照程序对对象进行初始化。 分配内存:指针碰撞和空闲列表。
指针碰撞:利用指针区分已使用内存和空闲内存。
选择哪种分配方式由 java 堆是否规整决定,而是否规整又由所采用的垃圾收集器是否带有空间压缩整理的能力决定。
另外一个需要考虑的问题是。在分配内存时进行修改指针的操作不是线程安全的,解决这个问题有两种可选方案:一种是使用CAS操作配上失败重试的方式保证更新操作的原子性;另外一种方式是把内存分配的动作按照线程划分在不同的空间之中进行,也就是前面提到的本地线程分配缓冲(TLAB)。只有某个线程的本地缓冲区用完了才需要同步锁定。可以通过-XX:+/-UseTLAB参数来设定是否使用该功能。

2.2.2 对象的内存布局

在HotSpot虚拟机里,对象在堆中的存储布局可以划分为三个部分:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。 对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,称为Mark Word。考虑到虚拟机的空间效率,Mark Word 被设计成一个有着动态定义的数据结构,以便在极小的空间内存储尽量多的数据,根据对象的状态复用自己的存储空间。

存储内容标志位状态
对象哈希码,对象年龄分代01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁定)
空,不需要记录信息11GC标记
偏向线程ID,偏向时间戳,对象分代年龄01可偏向

对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,也就是说查找对象的元数据信息并不一定要经过对象本身(见下一节)。
此外,如果对象是一个 Java 数组,对象头中还必须有一块用于记录数组长度的数据。

实例数据部分是对象存储的有效信息。相同宽度的字段总是被分配到在一起存放,在能满足这个前提条件的情况下,父类中定义的变量会出现在子类之前。如果HotSpot虚拟机的+XX:CompactFields 为 True ,则子类的较窄变量也允许插入父亲变量的空隙之中。

对齐填充,仅仅起着占位符的作用。

2.2.3 对象的访问定位

java 程序通过栈上的 reference 数据来操作堆上的具体对象。
主流的访问方式主要有使用句柄直接指针这两种。 HotSpot 主要采用了第二种方式。
使用句柄相当于二级索引。