JVM中一般情况下对象都是优先分配在堆空间的Eden区,但是有些情况下也是可以进行一些分配上的优化,本文通过两个小案例,演示一下TLAB分配和栈上分配的优化,测试版本JDK1.8。
TLAB分配
TLAB:本地线程分配缓冲,内存分配实际上被按照不同的线程划分在不同的内存之间进行,每个线程在Eden区中中有一块独享的小区域,这样做的好处是可以减少同步处理带来的性能消耗。
下面的小案例中启动了100个线程,如果没有TLAB优化,那么启动的线程越多,对象分配时的同步处理就越耗时。
首先配置如下参数启动,-XX:-UseTLAB -XX:-DoEscapeAnalysis,表示关闭TLAB分配,关闭逃逸分析,确保对象只能在堆上分配。
public class TestAlloc {
class User {
}
void alloc() {
new User();
}
public static void main(String[] args) throws InterruptedException {
TestAlloc t = new TestAlloc();
CountDownLatch countDownLatch = new CountDownLatch(100);
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 10_0000; j++) {
t.alloc();
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
执行耗时大约1秒。
修改配置参数启动,-XX:+UseTLAB -XX:-DoEscapeAnalysis,表示开启TLAB分配,关闭逃逸分析,确保对象只能在堆上分配。
执行耗时大约100毫秒,性能差距大约10倍。
栈上分配
几乎所有的对象都是分配在堆内存中,但是还有一种比较特殊的分配方式是分配在栈上,这是借助于逃逸分析来辅助实现的,逃逸分析中指出如果对象的作用域不会逃出方法或者线程之外,也就是无法通过其他途径访问到这个对象,那么就可以对这个对象采取一定程度的优化,这其中就包含了:栈上分配。
栈上分配的好处在于,对象可以随着栈的出栈过程被自然的销毁,节省了堆中垃圾回收所消耗的性能。
还是同样的案例,alloc方法中new出来的User对象,作用域只在改方法中,所以可以通过逃逸分析的结果,实现栈上分配。对象优先栈上分配,所以TLAB是否开启不影响,使用默认配置就行。
首先配置如下参数启动, -XX:-DoEscapeAnalysis,关闭逃逸分析。
public class TestAlloc {
class User {
}
void alloc() {
new User();
}
public static void main(String[] args) {
TestAlloc t = new TestAlloc();
long start = System.currentTimeMillis();
for (int j = 0; j < 1_0000_0000; j++) {
t.alloc();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
执行耗时大约300毫秒。
当配置开启逃逸分析时 -XX:+DoEscapeAnalysis,执行耗时只有10毫秒左右。