玩转JVM中的对象及引用:从创建到引用到分配和优化策略

本文详细探讨了JVM中对象的生命周期,从类加载检查、内存分配、初始化到垃圾回收的策略。介绍了对象头、实例数据和对齐填充的内存布局,以及引用计数和可达性分析两种对象生死判断算法。讨论了各种垃圾回收算法,如标记-清除、复制、标记-整理,以及分代收集。最后,文章提到了JVM的分代处理垃圾和不同类型的垃圾回收类型。
摘要由CSDN通过智能技术生成

类加载检查

当Java虚拟机遇到一条new指令的时候,它会先去运行时常量池中寻找new的类的符号引用,并且检查这个符号引用所代表的类是否已经被加载、解析、初始化过。如果没有即需要进行相应的类加载过程。

为新生对象分配Java堆内存

对象所需要的内存大小在Java类加载的时候已经确定下来了。为对象分配堆内存相当于把一块内存分出来放置对象。

主要分配内存的方式有两种:指针碰撞空闲列表

  • 指针碰撞:如果堆内存空间是规整的,那么,只需要将指针向空闲区域移动对象大小的内存即可以实现分配内存。
  • 空闲列表:维护一个空闲列表,记录哪些内存空间是可以使用的,在分配内存的时候,选取一块足够大的空间分配给对象实例,并更新空闲列表。

注意到对象创建在虚拟机执行的过程中是非常频繁的行为,仅仅修改一个指针所指向的位置,在并发情况下不是线程安全的。因此也有两种解决方案:

  1. 使用CAS并配上失败重试的方式保证更新操作的原子性。
  2. 给每一个线程在Java堆中预先分配线程私有分配缓冲区,哪个线程需要分配内存,只要在线程私有分配缓冲区中分配即可以。

将分配到的内存空间初始化零值

将分配到的内存空间初始化零值,这保证了实例字段不赋值可以直接使用。如果使用了TLAB,这一步可以提前到TLAB分配的时候进行。

对对象进行必要的设置

对象是哪个类的实例;

如何找到类的元数据信息;

对象的哈希码;

对象的GC分代年龄信息;

这些信息存在对象的对象头信息之中

构造函数

执行完以上四步,从虚拟机角度,一个对象已经产生了,但是对于java程序而言,构造函数还没有开始执行。接下来按照构造函数的要求,对对象进行初始化即可。

另:Java堆中对象的内存布局和访问定位

  1. 对象头主要包含两类信息。第一类是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针。
  2. 类型数据部分是对象真正存储的有效信息,即程序代码中定义的各种类型的字段内容。
  3. 对齐填充:任何对象的大小都必须是8字节的整数倍。

对象的访问定位:

  • 使用句柄访问的话,Java堆中将可能会划分出来一块内存来作为句柄池。Reference变量中存放的是句柄池的地址,句柄池中存放有到对象实例数据的指针以及到对象类型数据的指针。
  • 使用直接访问的话,reference变量中存放的是对象的实例数据、对象的实例数据中包含有到对象类型数据的指针。

对象的内存布局

问:在 Java 对象创建后,到底是如何被存储在Java内存里的呢?

答:在Java虚拟机(HotSpot)中,对象在 Java 内存中的 存储布局 可分为三块:

  • 对象头 存储区域
  • 实例数据 存储区域
  • 对齐填充 存储区域

①对象头 区域

此处存储的信息包括两部分:

  • 对象自身的运行时数据(Mark Word)

如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等

该部分数据被设计成1个 非固定的数据结构 以便在极小的空间存储尽量多的信息(会根据对象状态复用存储空间)

  • 对象类型指针

即对象指向它的类元数据的指针

虚拟机通过这个指针来确定这个对象是哪个类的实例

特别注意

如果对象是数组,那么在对象头中还必须有一块用于记录数组长度的数据!

因为虚拟机可以通过普通

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值