写点什么

对象的实例化内存布局与访问定位

用户头像
朱华
关注
发布于: 2020 年 10 月 13 日



思考:

  • 对象在 JVM 中是怎么存储的?

  • 对象头信息里面有哪些东西?



对象实例化



创建对象的方式

  1. new

  2. 常见方式

  3. 变形 1:单例模式中的 Xxx的静态方法

  4. 变形 2:XxxBuilder/XxxFactory 的静态方法

  5. Class 的 newInstance():反射的方式,只能调用空参的狗高企,权限必须是 public (JDK 9 失效)

  6. Constructor 的 newInstance(Xxx):反射的方式,可以调用空参,带参的构造器,权限没有要求

  7. 使用 clone():不调用任何构造器,当前类需要实现 Cloneable 接口,实现 clone()(浅复制)

  8. 使用反序列化

  9. 第三方库



创建对象的步骤

  1. 判断对象对应的类是否加载、连接、初始化



  1. 为对象分配内存



  • 如果内存规整 ---- 指针碰撞



  • 如果内存不规整

  1. 虚拟机需要维护一个列表

  2. 空闲列表分配



  • 说明



  1. 处理并发安全问题

  • 采用 CAS 失败重试、区域加锁保证更新的原子性

  • 每个线程预先分配一块 TLAB

通过 -XX:+/-UserTLAB 参数来设定

  1. 初始化分配到空间

所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用

  1. 设置对象头

  1. 执行 init 方法进行初始化



内存布局

对象头

包含两部分。说明:如果是数组,还需记录数组的长度

  • 运行时元数据(Mark Word)

  • 哈希值(HashCode)

  • GC 分代区

  • 锁状态标志

  • 线程持有的锁

  • 偏向线程 ID

  • 偏向时间戳

  • 类型指针

指向类元数据 InstanceKlass,确定该对象所属的类型



示例数据

它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)

规则:

  • 相同宽度的字段总是配分配在一起

  • 父类中定义的变量会出现在子类之前

  • 如果 CompactFields 参数为 true(默认为 true):子类的窄变量可能插入到父类变量的空隙



对齐填充

不是必须的,也没有特别含义,仅仅起到占位符的作用



案例:图示

public class Customer {
int id = 100;
String name;
Account account;
{
name = "匿名客户";
}
public Customer() {
account = new Account();
}
}
class Account {
}



public class CustomerTest {
public static void main(String[] args) {
Customer customer = new Customer();
}
}



对 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

发布于: 2020 年 10 月 13 日阅读数: 54
用户头像

朱华

关注

见自己,见天地,见众生。 2018.08.07 加入

还未添加个人简介

评论

发布
暂无评论
对象的实例化内存布局与访问定位