栈上分配
JVM中,栈上空间为线程私有,堆上空间为全局共享。所以大部分对象存在于堆上,线程通过栈上的引用指向堆上对象的内存地址。堆上没有任何引用关系的对象会被JVM标记后GC掉。
很多对象不存在逃逸现象,即线程专有,那么从栈到堆的寻址过程就显得有些多余了,而且还需要等到GC时才能清理该对象,不如直接将该对象创建在线程自身的栈帧中,随着线程销毁直接清除,这就叫栈上分配
。JDK7之后,栈上分配的对象会被JVM打散成一个一个的原始类型,这些原始类型就叫做“标量”,这个行为叫做标量替换
。每个标量可以被单独的分析和优化,这样就无需再创建原始对象了,即节省了创建对象的成本,又避免了对象清理带来的GC。栈上分配和标量替换都是JIT的优化机制,更多可见:JIT编译器的优化方式
举个例子,通过语文和英语考试分数相加计算总分:
class Score {
int chinese;
int english;
}
public int calculateTotalScore() {
Score score = new Score(1, 2);
return score.chinese + score.english;
}
JVM通过逃逸分析,发现score
对象并没有被方法外部引用,那么通过标量替换,会被编译为如下代码:
public int calculateTotalScore() {
int chinese = 1;
int english = 2;
return chinese + english;
}
TLAB
TLAB全名本地线程分配缓冲(Thread Local Allocation Buffer),栈上分配不了的对象,还是得分配到堆上,堆的特点是所有线程共享,那么大量线程同时在堆上申请创建空间就会发生同步争抢,效率低下。所以JVM默认给每一个线程在eden区中分配一个专有空间,那么对象创建时,在各自的空间中创建就不存在同步争抢的问题了。
这个空间不大,仅仅能存放小对象,大对象会直接分配到堆上。这是一种妥协设计,因为绝大多数的对象,都是小对象。
PLAB
除了Eden区,还有Survivor和老年代,它们也有各自的线程专有对象分配空间,叫PLAB(Promotion Local Allocation Buffers),即晋升本地分配缓存,PLAB和TLAB类似,都是为了避免多线程创建对象发生争抢而存在的,区别仅仅是作用区域不同。
总结
栈上分配是指线程创建私有对象时,在栈上创建而非堆,从而提升创建对象和清理对象的效率。而TLAB和PLAB是为了解决堆中对象创建时同步争抢问题。