全网最硬核 JVM TLAB 分析 2. TLAB生命周期与带来的问题思考

今天,又是干货满满的一天。这是全网最硬核 JVM 系列的开篇,首先从 TLAB 开始。因为文章很长,每一个人阅读习惯不一样,因此特此拆成单篇版和多篇版算法

4. TLAB 的生命周期

image

TLAB 是线程私有的,线程初始化的时候,会建立并初始化 TLAB。同时,在 GC 扫描对象发生以后,线程第一次尝试分配对象的时候,也会建立并初始化 TLAB。 TLAB 生命周期中止(TLAB 声明周期中止不表明内存被回收,只是表明这个 TLAB 再也不被这个线程私有管理)在:性能

  • 当前 TLAB 不够分配,而且剩余空间小于最大浪费空间限制,那么这个 TLAB 会被退回 Eden,从新申请一个新的
  • 发生 GC 的时候,TLAB 被回收。

5. TLAB 要解决的问题以及带来的问题与解决方案的思考

TLAB 要解决的问题很明显,尽可能避免从堆上直接分配内存从而避免频繁的锁争用。.net

引入 TLAB 以后,TLAB 的设计上,也有不少值得考虑的问题。线程

5.1. 引入 TLAB 后,会有内存孔隙问题,还可能影响 GC 扫描性能

出现孔隙的状况:设计

  • 当前 TLAB 不够分配时,若是剩余空间小于最大浪费空间限制,那么这个 TLAB 会被退回 Eden,从新申请一个新的。这个剩余空间就会成为孔隙。
  • 当发生 GC 的时候,TLAB 没有用完,没有分配的内存也会成为孔隙。

image

若是无论这些孔隙,因为 TLAB 仅线程内知道哪些被分配了,在 GC 扫描发生时返回 Eden 区,若是不填充的话,外部并不知道哪一部分被使用哪一部分没有,须要作额外的检查,那么会影响 GC 扫描效率。因此 TLAB 回归 Eden 的时候,会将剩余可用的空间用一个 dummy object 填充满。若是填充已经确认会被回收的对象,也就是 dummy object, GC 会直接标记以后跳过这块内存,增长扫描效率。可是同时,因为须要填充这个 dummy object,因此须要预留出这个对象的对象头的空间日志

5.2. 某个线程在一轮 GC 内分配的内存并不稳定

若是咱们能提早知道在这一轮内每一个线程会分配多少内存,那么咱们能够直接提早分配好。可是,这简直是痴人说梦。每一个线程在每一轮 GC 的分配状况可能都是不同的:对象

  • 不一样的线程业务场景不一样致使分配对象大小不一样。咱们通常会按照业务区分不一样的线程池,作好线程池隔离。对于用户请求,每次分配的对象可能比较小。对于后台分析请求,每次分配的对象相对大一些。
  • 不一样时间段内线程压力并不均匀。业务是有高峰有低谷的,高峰时间段内确定分配对象更多。
  • 同一时间段同一线程池内的线程的业务压力也不必定不能作到很均匀。极可能只有几个线程很忙,其余线程很闲。

因此,综合考虑以上状况,咱们应该这么实现 TLAB:blog

  • 不能一会儿就给一个线程申请一个比较大的 TLAB,而是考虑这个线程 TLAB 分配满以后再申请新的,这样更加灵活。
  • 每次申请 TLAB 的大小是变化的,并非固定的。
  • 每次申请 TLAB 的大小须要考虑当前 GC 轮次内会分配对象的线程的个数指望
  • 每次申请 TLAB 的大小须要考虑全部线程指望 TLAB 分配满从新申请新的 TLAB 次数
相关文章
相关标签/搜索