一个 Java 对象的诞生

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

1. 一个对象的诞生

当 JVM 通过程序计数器的行号读取到一条 new 指令的时候,首先会去方法区寻找这个类是否被加载、解析、初始化过,如果没有的话需要先进行类的加载。

类被加载之后需要在 Java 堆中分配内存,如果 Java 堆中的内存是非常规整的,记录着使用和未使用区域的分界线,那么内存分配仅仅是将分界线向没有使用的内存方向移动一段距离就可以了。这种方式称为"指针碰撞"。如果内存并不是规整的,那么虚拟机就需要单独维护一个列表,用来记录哪些内存是可用的。在分配的时候通过这个列表找到足够大的一块内存分配给该对象。这种方式称为"空闲列表"。

这里还需要注意一个并发的问题。如果 A 对象正在分配内存,指针位置还没有来得及修改,B 对象又从原来的位置开始分配内存。

为了解决这个问题,有两种方案。一种是将内存分配进行同步处理。保证内存分配的原子性。另外一种解决方式是根据线程的不同,将分配内存的动作划分到不同的区域。每个线程都在自己的内存区域申请内存。这个区域称为本地线程分配缓冲(TLAB)。当这部分区域的内存不够时再进行同步申请新的区域。

内存分配完成后,虚拟机将内存初始化为 0 值,一些属性的默认值都是在这一步赋值的。比如 int 类型、boolean 类型的默认值等。

然后需要对对象进行必要的设置。比如说对象的年龄、对象的哈希值、对象的类的信息等。这部分数据保存在对象头中

2. 对象的访问

对象创建结束后,有两种方式来访问我们创建的对象。使用句柄和直接指针。

2.1 使用句柄访问。

如果使用句柄访问的话虚拟机会多划出一块内存用来做句柄池。Java 虚拟机栈中记录的就是对象的句柄地址。如下图所示:

20190321-3

2.2 直接指针访问

使用直接指针访问时,Java 虚拟机的栈中记录的是对象在 Java 堆中的直接地址。如下图所示:

20190321-4

这两种方式各有优点,使用句柄访问时在对象移动的时候(GC时)只需要改变句柄的值就可以了,而 Java 栈中的引用地址可以不用改变。

直接指针方式最大的好处就是速度快,它减少了一次指针定位的时间开销。

3. 参考

Guess you like

Origin juejin.im/post/7032892195174613000