最近疫情很严重啊,大家尽量别出门戴好口罩保护好自己哈。在家翻翻深入理解 Java 虚拟机这本神书,有这样一段话:“对象的内存分配,往大方面讲,就是在堆上分配(但也可能经过 JIT 编译后被拆散为标量类型并间接地在栈上分配),对象主要分给在新生代的 Eden 区上,如果启动了本地线程分配缓冲,将按线程优先在 TLAB 上分配,少数情况下也可能会直接分配在老年代中…”
那么什么是 TLAB 呢?这玩意儿是干什么用的?一块学习一下。
首先我们先从 new 对象这个操作和指针碰撞来讲讲。
在 Java 中创建某个对象,并且调用此对象的方法是再经常不过的事了,但有些对象的生命周期是很短暂的,仅仅在方法内部被引用的话,没必要将对象分配到堆上,省得后续还得由 JVM 进行垃圾回收。
public void test(){
Girl girl = new Girl();
girl.gossiping();
}
上面的 girl 这个对象的作用域不会逃逸到方法体外,也就是说可以随着方法的调用结束而自动消亡。你还分配到堆上干啥?这不是添加JVM GC的负担么。
咱再来掰扯下什么叫做指针碰撞,假设堆内存是规整的,被一个指针给一分为二,左边是已给对象分配了的内存,指针后边是尚未分配的内存。来了一个对象分配的请求就将指针后移,这在单线程下操作没毛病,但多线程下的内存分配就可能出现指针碰撞。
线程A 为对象A分配完内存,还没来得及修改指针,线程B就开始为对象B分配内存了,此时的线程B仍然引用的是尚未被线程A修改的指针地址。这不就发生指针碰撞了么,此时的指针叫做线程共享资源。
咋办好呢?TLAB 登场,TLAB 全称就是:Thread Local Allocation Buffer,译为:线程本地分配缓冲区。是线程私有的内存分配区域。
有个虚拟机参数“-XX:UseTLAB”,如果设置了此参数,那么线程在初始化时,会申请一块 size 固定的内存,仅为当前线程使用。每个线程都有了自己的独立的内存分配空间后,就不存在内存分配竞争的关系了,同时,分配的效率也提升了。
当然了,TLAB的出现只是为了让每个线程有各自的内存分配指针,而这些线程所创建的对象仍然是可以被所有线程访问的。
再来讲讲 TLAB 令人诟病的地方。
首先,TLAB 大小固定,为了减少空间浪费,一般都很小,如果给一个大对象分配内存,可能会放不下。(譬如 100kb的 TLAB,有个200kb 的对象需要分配)。
其次,TLAB 空间没有被完全使用完毕(譬如 100kb 的 TLAB,分配了90kb,又来了一个 20kb 的对象)。因此,JVM 里有有个参数:_refill_waste_limit,即最大浪费空间。当 TLAB 中剩余可分配空间小于此值,当前线程会重新向 Eden 区申请一个 TLAB 空间,以便后续的对象创建。
还有一点,由于 TLAB 允许空间浪费,导致整个 Eden 区空间不连续,积少成多,产生不少碎片,后续 eden 区域空间不足时会触发新一轮的 GC。