今天,又是干货满满的一天。这是全网最硬核 JVM 系列的开篇,首先从 TLAB 开始。因为文章很长,每一个人阅读习惯不一样,因此特此拆成单篇版和多篇版算法
- 全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)
- 全网最硬核 JVM TLAB 分析 1. 内存分配思想引入
- 全网最硬核 JVM TLAB 分析 2. TLAB生命周期与带来的问题思考
- 全网最硬核 JVM TLAB 分析 3. JVM EMA指望算法与TLAB相关JVM启动参数
- 全网最硬核 JVM TLAB 分析 4. TLAB 基本流程全分析
- 全网最硬核 JVM TLAB 分析 5. TLAB 源代码全解析
- 全网最硬核 JVM TLAB 分析 6. TLAB 相关热门Q&A汇总
- 全网最硬核 JVM TLAB 分析(额外加菜) 7. TLAB 相关 JVM 日志解析
- 全网最硬核 JVM TLAB 分析(额外加菜) 8. 经过 JFR 监控 TLAB
这里我会持续更新的,解决你们的各类疑问数组
主要保证 GC 的时候扫描高效。因为 TLAB 仅线程内知道哪些被分配了,在 GC 扫描发生时返回 Eden 区,若是不填充的话,外部并不知道哪一部分被使用哪一部分没有,须要作额外的检查,若是填充已经确认会被回收的对象,也就是 dummy object, GC 会直接标记以后跳过这块内存,增长扫描效率。反正这块内存已经属于 TLAB,其余线程在下次扫描结束前是没法使用的。这个 dummy object 就是 int 数组。为了必定能有填充 dummy object 的空间,通常 TLAB 大小都会预留一个 dummy object 的 header 的空间,也是一个 int[]
的 header,因此 TLAB 的大小不能超过int 数组的最大大小,不然没法用 dummy object 填满未使用的空间。缓存
当从新分配一个 TLAB 的时候,原有的 TLAB 可能还有空间剩余。原有的 TLAB 被退回堆以前,须要填充好 dummy object。这样致使这块内存没法分配对象,所示被称为“浪费”。若是不限制,遇到 TLAB 剩余空间不足的状况就会从新申请,致使分配效率下降,大部分空间被 dummy object 占满了,致使 GC 更加频繁。markdown
TLABWasteTargetPercent 描述了初始最大浪费空间配置占 TLAB 的比例post
首先,最理想的状况就是尽可能让全部对象在 TLAB 内分配,也就是 TLAB 可能要占满 Eden。 在下次 GC 扫描前,退回 Eden 的内存别的线程是不能用的,由于剩余空间已经填满了 dummy object。因此全部线程使用内存大小就是 下个 epcoh 内会分配对象指望线程个数 * 每一个 epoch 内每一个线程 refill 次数配置
,对象通常都在 Eden 区由某个线程分配,也就全部线程使用内存大小就最好是整个 Eden。可是这种状况太过于理想,总会有内存被填充了 dummy object而形成了浪费,由于 GC 扫描随时可能发生。假设平均下来,GC 扫描的时候,每一个线程当前的 TLAB 都有一半的内存被浪费,这个每一个线程使用内存的浪费的百分比率(也就是 TLABWasteTargetPercent),也就是等于(注意,仅最新的那个 TLAB 有浪费,以前 refill 退回的假设是没有浪费的):fetch
1/2 * (每一个 epoch 内每一个线程指望 refill 次数) * 100
优化
那么每一个 epoch 内每一个线程 refill 次数配置就等于 50 / TLABWasteTargetPercent
, 默认也就是 50 次。spa
当分配出来 TLAB 以后,根据 ZeroTLAB 配置,决定是否将每一个字节赋 0。在 TLAB 申请时,因为申请 TLAB 都发生在对象分配的时候,也就是这块内存会马上被使用,并修改赋值。操做内存,涉及到 CPU 缓存行,若是是多核环境,还会涉及到 CPU 缓存行 false sharing,为了优化,JVM 在这里作了 Allocation Prefetch,简单理解就是分配 TLAB 的时候,会尽可能加载这块内存到 CPU 缓存,也就是在分配 TLAB 内存的时候,修改内存是最高效的。线程
在建立对象的时候,原本也要对每一个字段赋初始值,大部分字段初始值都是 0,而且,在 TLAB 返还到堆时,剩余空间填充的也是 int[] 数组,里面都是 0。日志
因此,TLAB 刚分配出来的时候,赋 0 避免了后续再赋 0。也能利用好 Allocation prefetch 的机制适应 CPU 缓存行(Allocation prefetch 的机制详情会在另外一个系列说明)
根据以前的分析,每一个线程的 TLAB 的大小,会根据线程分配的特性,不断变化并趋于稳定,大小主要是由分配比例 EMA 决定,可是这个采集是须要必定运行次数的。而且 EMA 的前 100 次采集默认是不够稳定的,因此 TLAB 大小也在程序一开始的时候变化频繁。当程序线程趋于稳定,运行一段时间后, 每一个线程 TLAB 大小也会趋于稳定而且调整到最适合这个线程对象分配特性的大小。这样,就更接近最理想的只有 Eden 区满了才会 GC,全部 Eden 区的对象都是经过 TLAB 分配的高效分配状况。这就是 Java 代码越执行越快在 TLAB 方面的缘由。