JavaSE高级篇:HotSpot虚拟机对象探秘

在这里插入图片描述


这里我们使用最常用的HotSpot虚拟机和Java堆为例,深入单套HotSpot虚拟机在Java堆中的:对象分配、布局和访问的全过程。

第一章:对象的创建

Java程序在一个一个的线程运行的过程中,无数的对象被创建出来。这里讨论的对象的创建仅仅是通过new的方式创建的对象,不包括数组和Class对象。

一:内存分配前的两个校验

当虚拟机遇到一条new指令的时候,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用。检查这个符号对应的类是否已经被加载、解析和初始化过,如果没有的话,先进行类加载的过程。

类加载检查通过之后,JVM为新生对象分配内存。对象所需要在内存大小在类加载完成之后就已经存在了(为对象分配空间是加上等同于把一块确定)

二:分配对象空间

指针碰撞:假设Java堆内存中的是绝对规整的(所有被使用过内存都放到了一边,空闲的内存在一边),中间放着一个指针作为分界点指示器。
空闲列表:Java堆内存不是规整的,已经使用的内存和未使用的内存交织在一起。虚拟机需要维护一个列表,记录那些内存块是可用的,分配的时候找一块足够大的内存区域进行分配,并在表中维护记录。

对象分配划分空间会遇到的问题:
对象创建在虚拟机当中十分频繁,仅仅靠修改指针指向位置在并发情况下并非线程安全。可能出现:对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来进行分配内存。
解决这种问题有两个解决方案:对分配内存空间的操作进行同步处理(虚拟机上采用的CAS配上失败重试的方式保证更新操作的愿意性),另外一种就是内存分配按照线程划分在不同的空间进行,每个线程在堆当中有一块本地分配线程缓冲区TLAB,只有该缓冲区使用完了,分配新的缓冲区时才需要进行同步锁定,虚拟机是否需要使用TLAB可以通过-XX:±/UseTLAP来进行设置。

三:对象初始化

对象内存分配完成之后,虚拟机需要将对象的都初始化为零值(不包括对象头),保证对象的实例字段不可以赋值就可以直接使用。如果虚拟机开启了TLAB这块内从在分配内存的时候顺便就做了。

四:对象对象头的设置

虚拟机对对象的对象头进行必要的设置:这个对象是哪个类的实例,如何找到类的元数据,对象的哈希码(真正调用Object.hashcode()的时候才会进行计算),对象GC分带年龄,

以上从虚拟角度来讲,一个对象已经产生了,但是从Java程序角度来看对象创建才刚刚开始,当前构造方法()方法还没有执行所有的字段都是还是默认的零值。对象所需要的其他的资源和状态也没有按照意图构造好。构造方法执行完毕之后,一个真正有用的对象才算真正的被构造出来。

第二章:对象的内存布局

在HotSpot虚拟机中,对象在内存中存储布局分为三个部分:对象头+实例数据+对齐填充

一:对象头存储信息分类

对象头存储信息有两种:对象自身运行时数据+类型指针

1:对象运行时数据

对象自身运行时数据包括:哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳。未开启压缩指针的前提下,这部分数据长度在32和64位虚拟机分别是32和64个比特,官方将这部分内容称之为Mark Word

32位虚拟机其中一种存储状态为:25比特存哈希码值,4个比特存分代年龄,2个比特存锁标志位,一个比特固定为0

2:类型指针

对象头另外一部分是类型指针,对象指向他的类型元数据的指针,JVM虚拟机通过这个类型指针找到对象该对象是哪个类的实例。当然,并不是所有Java虚拟机都会对象头上保留乐行指针,换句话说查询查找对象的元数据不一定通过对象的本身

如果对象是个Java数组,对象头上还会有一个区域记录数组长度。虚拟机可以通过对象的元数据信息推断出对象的大小,但是数据的长度是不确定的,无法通过元数据信息推断出数组的大小。

二:实例数据部分

实例数据部分存储的是对象部分真实有效的信息,即我们在程序代码中定义的各种类型的字段内容,无论是父类继承下来的,还是在子类中定义的字段都必须记录下来(注意,这里只提到了字段),字段记录顺序会受到-XX:CompactFields参数和源码中字段定义顺序的影响。
存放顺序:按类型存放,相同宽度放一起,在此基础之上父类变量在子类之前。

三:对齐填充

对象都是8字节的整数倍,对象头部分以满足,实例数据不满足的话,需要对齐填充来补齐。

第三章:对象的访问定位

对象创建完之后就是为了使用对象,JVM会通过栈上的reference数据操作堆上的具体对象。reference类型在《Java虚拟机规范中》只规定是他是一个对象的引用。对象的访问方式也是有具体的虚拟机实现的。主流的有两种:

一:句柄

句柄访问的话,在堆中划分出来一块内存作为句柄池,reference当中存储的是对象句柄池中的地址。句柄池中包含了对象的实例数据和类型数据的地址
在这里插入图片描述

二:直接指针

使用直接指针的话,reference存储的就是直接的对象地址,只访问对象本身的话,就不需要多一次间接访问开销。但是这种Java中的堆中对象需要考虑如何放置对象的类型信息(对象头)

在这里插入图片描述

三:总结比较

句柄最大好处就是reference当中存储的稳定的句柄地址,对象被移动后(垃圾收集时移动对象很普遍)只会改变句柄中的对象实例的地址,reference本身不会改变。

直接指针的放回寺就是速度快,节省了一次指针定位的时间开销。HotSpot使用的就是这个,当然此虚拟机有额外情况,使用shenandoah收集器的话也会有一次额外的转发。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你们出来吧出来吧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值