jvm-对象的创建、布局、访问
11 May 2020之前介绍过java虚拟机的运行时数据区域,这篇文章简单介绍java对象在运行时数据区域的是如何创建、布局和访问的(HotSpot虚拟机)。
创建
java程序执行 new Object()
时就是要创建对象了。java虚拟机收字节码的new指令时,首先会去常量池中检查是否能找到Object的符号引用(Object对象的以符号形式的书面描述,存放在.class文件中),并检查这个符号引用所代表的的累是否已被加载、解析、初始化过(初始化后,表明这个类已经创建完成了)。如果没有,那必须先执行Object的加载过程。
当类加载检查通过后,就需要为Object分配内存空间。这里有两种内存空间分配方式:指针碰撞和空闲列表,如果内存空间是规整的,使用指针碰撞,分配内存就是将指针向未被奉陪的内存区域方向移动一定距离;如果内存是“坑坑洼洼”的,使用空闲类表,在类表中找出一块满足需求的内存空间分配给Object,并且更新列表。对象的内存空间一般会分配在堆区域,这块区域是线程共享的,当不同线程中频繁创建对象时,会出现并发现象:对象A的内存分配了,但是指针还没来得及移动位置或者列表还没有及时更新时,又要给B分配内存空间,很可能会造成因为给B分配内存空间而把A给“抹掉”了。本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)可以解决并发分配问题,就是将堆按照线程分配成不同的空间,每个线程都有一个属于自己的小空间(这个空间被称为本地线程分配缓冲),哪个线程要创建对象就在它对应的小空间中分配内存。
内存分配完成后,java虚拟机必须将分配的内存空间初始化成零值(对象的字段类型所对应的零值),这个操作保证了java实例的字段即使没有赋初始值也能使用,这个初始化跟java object默认的无参构造函数初始化指的是同一个东西吗?后期查证。如果java程序的代码是 new Object(parameters)
那还需要执行这个指定的构造方法,按照开发者的意向对成员变量进行初始化,这样目标对象就完整的被创建出来了。
总结起来就三步:
- 检查.class文件中的符号应用并且加载并检查。
- 给Object分配内存空间,并且初始化成“零”值。
- 调用开发者指定的 object 构造函数初始化成员变量。
布局
对象在堆内存中的存储布局可以划分为三个部分:对象头,实例数据,对齐填充。
对象头(Header)部分包括两类分信息:一类是用于存储对象自身信息的,比如:哈希码,GC分带年龄等,另一类是类型指针,即该对象指向它的类型元数据(在方法区里存放着)的指针,java虚拟机通过这个指针来确定该对象是哪个类的实例。
实例数据(Instance Data):这块内容就是开发者在类中定义的各种类型的成员变量(也叫字段),无论是从父类继承的还是自己定义的,都在这块分布着。实例数据的引用实际存放在栈中。
对齐填充(Padding):由于HotSpot虚拟机要求任何对象的大小必须是8字节的整数倍,所以实例数据部分的成员变量如果没有对齐,就要通过填充来对齐。这部分开发者不用管。
访问
对象的引用在java栈中,对象实体在堆中,对象对应的类元数据信息在方法区。java程序会通过栈上的引用来访问对象。目前主流使用方式有使用句柄和直接指针两种,Hotspot使用直接指针的方式(栈中的引用中直接存储对象的地址),好处就是访问速度快,省了一次指针定位的时间。