分代GC前置概念(二):TLAB

1. 对象内存布局

在HotSpot虚拟机中,对象在内存中的存储包括三个部分:

  1. 对象头信息
  2. 实例数据信息
  3. 对齐填充信息

头信息(Header)
头信息是对象非常重要的身份信息,由对象标记和类型指针两部分组成,且大小固定。

  • 对象标记:也就是MarkWord,是头信息中最重要的部分,记录着对象的各种锁状态、GC标记等。
  • 类型指针:指向对象的类元数据的指针,可能存在指针压缩。

对头信息感兴趣可以参照这篇文章:详解Mark Word

实例数据(Instance Data)
记录着对象真正的数据,即代码中定义的各种类型的字段数据。

对齐填充(Padding)
HotSpot虚拟机要求对象在内存中的起始地址必须为8字节的整数倍,这就要求所有对象的大小必须为8字节的整数倍,因此当某个对象的数据长度不满足时,则通过此部分进行填充对齐。

2. 新对象内存分配

当我们要在堆上创建一个对象的时候,由于对象头信息的大小固定,根据要创建的对象的类型又可以拿到对象中各个字段的类型,从而确定实例数据的大小,再加上对齐填充部分会将最终长度填充到8字节的整数倍,这样就可以做到在创建对象之前就已经确定了对象的实际大小。

此时要做的,就是在堆上找到一块足够大的内存分配给这个对象。寻找分配内存的方式有指针碰撞和空闲列表两种。

2.1 指针碰撞(Bump The Pointer)

当内存排列规整、已占用内存总是从一侧依次排列的情况时,可以通过指针碰撞的方式寻找。

在这里插入图片描述

2.2 空闲列表(Free List)

当内存排列不规整、已占用内存和未占用内存互相交错的情况时,可以通过空闲列表的方式寻找。此时虚拟机维护一张空闲内存块的列表,当创建对象时,会从列表中找到一个足够大的内存块分配给对象。

在这里插入图片描述

2.3 组合使用

在实际环境中,内存分布很规整的情况很少,随着内存的分配与回收,内存碎片的存在非常常见,所以往往是通过指针碰撞和空闲列表结合使用的方式来进行管理。

在这里插入图片描述

3. TLAB(Thread Local Allocate Buffer)

3.1 含义

虚拟机管理着从操作系统申请到的一块堆内存,所有虚拟机内部的对象创建,都需要从这块堆内存中进行分配。而执行创建对象操作的请求可能来自于不同的线程,如果每个请求都直接从这一整块堆内存中进行分配的话,为了保证程序的正确,就需要频繁进行线程同步,这无疑是很浪费时间的。

为了解决这个问题,VM为每个线程都分配了一小块堆内存,这一小块堆内存归该线程私有,称为该线程的TLAB, 即 “线程本地分配缓存”。

在这里插入图片描述

在启用了TLAB的情况下,当线程需要创建对象时,会经过如下过程:

  • 如果自身TLAB剩余空间足够,则直接从TLAB中进行分配,从而避免复杂的线程同步操作
  • 如果自身TLAB剩余空间不足,检测此时TLAB剩余空间是否大于某个限定值(最大浪费空间限制)
  • 如果小于限定值,将当前TLAB返还给虚拟机,然后虚拟机申请一块新的TLAB进行分配
  • 如果大于限定值,保留当前TLAB,但本次直接从虚拟机持有的内存中进行分配

在这里插入图片描述

3.2 最大浪费空间限制

上面提到了一个限定值——最大浪费空间限制,为什么会有这个值,这个值的作用是什么呢?

当TLAB被某个线程返还给虚拟机的时候,绝大部分情况下,其中都会有一部分还没有被分配出去的内存,当整块TLAB被GC回收之前,这块空闲的内存不会再被利用,于是就成了被浪费掉的内存。

而设置最大浪费空间的意义就在于,当线程试图向虚拟机申请换新的TLAB之前,可以通过这个值来衡量当前要返还的TLAB剩余的空间是不是足够大。

如果剩余空间小于这个限定值,可以认为当前TLAB已经被利用的差不多了,这点剩下的空间,以后每次创建新对象可能都不够用了,于是可以给线程换一个新的。

但是如果剩余空间超过了限定值,那就认为当前TLAB还有足够的空间没被利用呢,只是这次创建的对象可能占地比较大,下回创建个别的对象的时候还能接着用,没必要换新的:你小子别败家,这个TLAB你留着下回接着用。

3.3 分配与回收时机

分配时机:

  • 线程创建的时候
  • 线程在GC结束后第一次申请内存时
  • 线程创建对象时可能申请新的TLAB

回收时机

  • GC时回收所有TLAB
  • 线程创建对象时可能返还当前TLAB

3.4 TLAB的大小

理想状况下,我们希望:

  1. 在一个GC周期内,每个线程的创建对象工作都在自己的TLAB中完成,即TLAB足够大。
  2. 在GC开始时,每个线程的TLAB都几乎被分配完,即TLAB不会过大。

显然要完全达到上面两点是不可能的,但是可以通过算法结合历史经验数据对TLAB的大小做动态调整,从而使其分配的结果更加合理。

为此,JVM设计了一堆乱七八糟的公式,其中涉及到的参数也很多,比如线程数量,线程单个GC周期最多申请TLAB次数之类的。具体我也没看懂,有感兴趣的自己研究这篇文章吧:一箱关于TLAB的干货

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值