本文将从JVM内存区域划分、对象的创建过程、对象内存布局以及对象的访问定位等方面,深入剖析Java虚拟机对象管理机制,为读者呈现一个清晰、全面的知识体系
一、JVM内存区域划分 JVM在执行Java程序时,会将其所管理的内存划分为若干个不同的数据区域,这些区域各自承担着不同的职责
1.程序计数器(Program Counter Register) 程序计数器是线程私有的内存区域,用于记录当前线程所执行的字节码指令的地址
它是较小的内存空间,用作当前线程所执行的字节码的信号指示器
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令
因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程之间互不影响,独立存储
2.Java虚拟机栈(Java Virtual Machine Stack) Java虚拟机栈也是线程私有的,它的生命周期与线程相同
每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息
局部变量表存放了各种基本数据类型、对象引用、returnAddress类型等
其中,64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量的空间是完全确定的,在方法运行期间不会改变局部变量表的大小
3.本地方法栈(Native Method Stack) 本地方法栈与Java虚拟机栈的作用类似,但它允许执行Native方法
Native方法是使用非Java语言编写的,并且被编译为本地代码的方法
4.堆内存(Heap) 堆内存是所有线程共享的内存区域,用于存放对象实例和数组
堆内存是垃圾回收的主要区域,它根据不同的垃圾回收机制进行划分和管理
随着JIT编译器的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象的分配不再绝对依赖于堆内存
5.方法区(Method Area) 方法区同样是所有线程共享的内存区域,它存储了已被虚拟机加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据
虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它却有一个别名叫做No-Heap(非堆),目的是与Java堆区分开来
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用
二、对象的创建过程 在JVM中,对象的创建是一个复杂而精细的过程,它涉及多个步骤和内存区域的协同工作
1.类加载检查 当虚拟机遇到一条new指令时,它首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化
如果没有,那么必须先执行相应的类加载过程
2.内存分配 类加载通过后,虚拟机将为新生对象分配内存
对象所需内存的大小在类加载完成后便可以完全确定
内存分配方式可以分为“指针碰撞”和“空闲列表”两种方式
选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定
内存分配可能存在并发问题,因此JVM采用CAS配上失败重试的方式保证更新操作的原子性,或者使用本地线程分配缓冲(TLAB)来避免多线程同时分配内存时的冲突
3.内存初始化 内存分配完成后,虚拟机要将分配到的内存空间都初始化为零值(不包括对象头),这样就保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值
4.设置对象头信息 接下来,虚拟机设置对象头信息
对象头包含了对象自身的运行时数据(如哈希码、GC分代年龄、锁信息等)和类型指针(即对象指向它的类元数据的指针)
这些信息对于JVM来说至关重要,因为它们决定了对象的身份和行为
5.执行方法
最后,虚拟机执行 这样,一个真正可用的对象就产生了
三、对象内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头、实例数据和对齐填充
1.对象头(Object Header)
对象头包含了两部分信息:第一部分是Mark Word,它存储了与对象相关的信息,包括哈希码、GC分代年龄、锁信息等;第二部分是类型指针,它指向对象所属类的元数据(如Class对象),即对象的类型信息 Mark Word是一个变长的结构,具体长度根据JVM的实现而异
2.实例数据(Instance Data)
实例数据是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容 这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在源码中定义顺序的影响
3.对齐填充(Padding)
对齐填充起着占位符的作用,它确保对象的起始地址是8字节的正整数倍,以满足HotSpot自动内存管理系统的要求 对齐填充不会用于存储数据,但它对于对象的内存布局和访问效率有着重要影响
四、对象的访问定位
在JVM中,对象的访问定位取决于虚拟机的实现方式 目前主流的访问方式有两种:句柄访问和直接指针访问
1.句柄访问
在句柄访问方式中,Java堆中会划分出一块内存来作为句柄池 引用中存储的是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息 句柄访问的最大好处是引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要被修改
2.直接指针访问
在直接指针访问方式中,引用中存储的是直接的对象地址 这种方式的好处是速度更快,因为它节省了一次指针定位的时间开销 由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项非常可观的执行成本 HotSpot虚拟机采用的就是直接指针访问方式
五、总结
Java虚拟机对象管理机制是一个复杂而精细的系统,它涉及JVM内存区域的划分、对象的创建过程、对象内存布局以及对象的访问定位等多个方面 通过深入了解这些机制,我们可以更好地理解Java程序的内存管理和对象访问方式,从而编写出更加高效、稳定的Java程序 同时,我们也可以利用JVM提供的各种优化技术和工具来进一步提升程序的性能和可靠性