对象的实例化内存布局与访问定位
思考:
对象在 JVM 中是怎么存储的?
对象头信息里面有哪些东西?
对象实例化
创建对象的方式
new
常见方式
变形 1:单例模式中的 Xxx的静态方法
变形 2:XxxBuilder/XxxFactory 的静态方法
Class 的 newInstance():反射的方式,只能调用空参的狗高企,权限必须是 public (JDK 9 失效)
Constructor 的 newInstance(Xxx):反射的方式,可以调用空参,带参的构造器,权限没有要求
使用 clone():不调用任何构造器,当前类需要实现 Cloneable 接口,实现 clone()(浅复制)
使用反序列化
第三方库
创建对象的步骤
判断对象对应的类是否加载、连接、初始化
为对象分配内存
如果内存规整 ---- 指针碰撞
如果内存不规整
虚拟机需要维护一个列表
空闲列表分配
说明
处理并发安全问题
采用 CAS 失败重试、区域加锁保证更新的原子性
每个线程预先分配一块 TLAB
通过 -XX:+/-UserTLAB
参数来设定
初始化分配到空间
所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
设置对象头
执行 init 方法进行初始化
内存布局
对象头
包含两部分。说明:如果是数组,还需记录数组的长度
运行时元数据(Mark Word)
哈希值(HashCode)
GC 分代区
锁状态标志
线程持有的锁
偏向线程 ID
偏向时间戳
类型指针
指向类元数据 InstanceKlass,确定该对象所属的类型
示例数据
它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
规则:
相同宽度的字段总是配分配在一起
父类中定义的变量会出现在子类之前
如果 CompactFields 参数为 true(默认为 true):子类的窄变量可能插入到父类变量的空隙
对齐填充
不是必须的,也没有特别含义,仅仅起到占位符的作用
案例:图示
对 CustomerTest 进行编译后查看。
图示:
对象访问定位
创建对象的目的是为了使用它
JVM 是如何通过栈帧中的对象引用访问到器内部的对象实例的呢?定位,通过栈上 reference 访问
对象访问方式主要有两种
句柄访问
直接指针(Hotspot 采用)
两种访问方式比较
句柄访问
最大的好处是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
指针访问
最大的好处时速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本。
参考资料
https://blog.csdn.net/yb970521/article/details/108015984
https://www.bilibili.com/video/BV1PJ411n7xZ?from=search&seid=56237391703306967
版权声明: 本文为 InfoQ 作者【朱华】的原创文章。
原文链接:【http://xie.infoq.cn/article/30bc71ecc4f532abdc258151f】。未经作者许可,禁止转载。
评论