Java基础知识点总结系列(九)—— 指针碰撞与TALB
一、 Java中的指针碰撞与TLAB
1. 指针碰撞
Java中提到指针碰撞主要是在GC中,GC的标记/整理(标记/压缩)算法在每次执行完之后会通过一个指针将堆内存分为两个区域,分别是已用区域和未用区域。指针的左边已用区域内存满了对象(其实就是经过上一次GC之后存活下来的对象),指针的右边为可用区域。之后当需要new一个对象时,JVM会在当前指针所指位置为新对象分配内存,并将指针向后移动对象所对应size
位。比如new的对象需要128字节的内存空间,那么JVM将会从当前指针所指位置开始,之后的128字节分配给该对象,同时指针后移128个字节,这就称之为指针碰撞。
看完指针碰撞后,大家很容易发现一个问题,因为堆内存本身是线程共享的,在多线程场景下,当一个线程需要创建对象,这时指针还没来得及修改(指针是在新对象占完位之后才能进行修改),如果另一个线程也需要分配空间,就会造成两个对象空间冲突。针对这个问题,提出了TALB。
2. TLAB(Thread Local Allocation Buffer 线程本地分配缓存区)
2.1 TALB的思路
TALB的解决思路比价简单粗暴,既然是因为堆内存多线程共享引发了问题,那我就直接给每个线程一个私有的区域分配对象不就解决了吗?
2.2 TALB的原理
TALB就是在堆内存上额外为每个线程分配一块线程私有区域,其大小一般比较小,默认占Eden区的1%。其本质就是通过start、top、end
三个指针实现,其中start和end分别指向这个TALB的开始和结尾位置,用于确定该TALB在堆上对应区域,避免其他线程再过来分配内存,top实时指向TALB区域内当前可分配的第一个位置,当一个TALB满了或剩余空间不足以存储新申请的对象时,线程话会向JVM再申请一块TALB。到这里为知,指针碰撞多线程问题似乎已经得到了解决,不过由于TALB空间本身较小(默认只占Eden区1%),所以就很容易出现TALB剩余区域不足以存储新对象的情况,这时线程会把新对象存到新申请的TALB中,这样原有的TALB中剩余区域就会被浪费,造成内存泄漏。于是又提出一个最大浪费大小的概念。
2.3 最大浪费空间
由于TALB内存浪费现象较为严重,所以JVM开发人员提出了一个最大浪费空间对TALB进行约束。
当TALB剩余空间存不下新对象时,会进行一个判断:
① 如果当前TALB剩余空间小于最大浪费空间,则TALB所属线程会向JVM申请一个新的TALB区域存储新对象,如果依旧存储不下,则对象会放在Eden区创建。
② 如果当前TALB剩余空间大于最大浪费空间,则对象直接去Eden区创建。
2.4 TALB的局限性
虽然TALB解决了指针碰撞在多线程场景下的问题,并且通过最大浪费空间可以减少内存泄漏,但其本身依旧有一些缺点:
① 相比于直接在Eden区进行内存分配,TALB的分配更容易触发GC(可以理解为,原本只有当Eden区满才会GC,而现在由于每个TALB都要比线程实际需要的大小要多占用一些内存(因为不可能每个TALB都刚好存满));
② TALB允许内存浪费,会导致Eden区内存不连续。