最近,我一直在研究遭受严重性能问题的Java应用程序。 在许多问题中,真正引起我注意的一个问题是新对象的分配速率相对较低(应用程序分配了大量的相当大的对象)。 后来发现,原因是在TLAB之外发生了大量分配。
什么是TLAB?
在Java中,新对象在Eden中分配。 这是线程之间共享的内存空间。 如果考虑到多个线程可以同时分配新对象,那么显然需要某种同步机制。 怎么解决呢? 分配队列? 某种互斥锁? 即使这些是不错的解决方案,也有更好的解决方案。 这就是TLAB发挥作用的地方。 TLAB代表线程本地分配缓冲区,它是Eden内部的一个专为线程分配的区域。 换句话说,只有一个线程可以在该区域分配新对象。 每个线程都有自己的TLAB。 因此,只要在TLAB中分配对象,就不需要任何类型的同步。 在TLAB内部进行分配很简单
指针缓冲 (这就是为什么有时将其称为指针缓冲分配)
–因此将使用下一个空闲内存地址。
TLAB变得满满的
可以想象,TLAB不是无限的,在某些时候它开始变满。 如果线程需要分配一个不适合当前TLAB的新对象(因为它几乎已满),则会发生两件事:
- 线程获取新的TLAB
- 该对象在TLAB之外分配
JVM根据几个参数决定将要发生的情况。 如果选择了第一个选项,则线程的当前TLAB将“退休”,并且分配将在新的TLAB中完成。 在第二种情况下,分配是在Eden的共享区域中完成的,这就是为什么需要某种同步的原因。 通常,同步是有代价的。
物体太大
默认情况下,将针对每个线程分别动态调整TLAB的大小。 根据Eden的大小,线程数及其分配率重新计算TLAB的大小。 更改它们可能会影响TLAB的规模-但是,由于分配率通常会有所不同,因此没有简单的公式可以解决。 当线程需要分配一个永远无法放入TLAB的大对象(例如,大数组)时,它将在Eden的共享区域中分配,这又意味着同步。 这正是我的应用程序中正在发生的事情。 由于某些对象太大,因此它们从未在TLAB中分配。
在TLAB之外分配一些对象不一定是一件坏事–这是在次要GC之前发生的典型情况。 问题是,与TLAB内部相比,TLAB外部存在大量分配。 在这种情况下,有两个可用选项:
- 使物体变小
- 尝试调整TLAB尺寸
就我而言,手动调整TLAB大小不是最佳选择。 众所周知,只有少数对象类型在TLAB之外分配。 通常,修复代码是最好的选择。 在我将对象显着减小之后,它们已装入TLAB,并且TLAB内部分配给TLAB外部分配的比率恢复正常。
翻译自: https://www.javacodegeeks.com/2019/03/thread-local-allocation-buffers.html