JVM–堆--笔记
使用逃逸分析,编译器可对代码做如下优化:
- 栈上分配:将堆分配转化为栈分配,如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
- 同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
- 分配对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分或全部可以不存储在内存,而是存储在CPU寄存器中。
栈上分配
-XX:-DoEscapeAnalysis与-XX:+DoEscapeAnalysis 做对比,后者运行时间更短,而且没有进行GC
package learn.jvm.simple.heap;
/*
栈上分配测试
-Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
与-XX:+DoEscapeAnalysis 做对比,后者运行时间更短,而且没有进行GC
*/
public class StackAllocation {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println((end - start) + " ms");
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void alloc() {
User user = new User();
}
static class User{
}
}
同步省略
public class SynchronizedTest {
public void f(){
Object hollis = new Object();
synchronized (hollis){
System.out.println(hollis);
}
}
}
hollis只被一个线程使用,synchronized会被省略。
分配对象或标量替换
- 标量(Scalar)是指一个无法再分解为更小的数据的数据。Java中的原始数据类型就是标量。
- 相对的,那些还可以再分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为它可以分解为其它聚合量和标量。
- 在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解为若干个其中包含的若干个成员变量来代替,这个过程就叫标量替换。
public static void main(String[] args) {
alloc();
}
private static void alloc() {
Point point = new Point(1,2);
System.out.println("point.x=" + point.x + ";point.y=" + point.y);
}
static class Point{
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
经过标量替换后变成:
private static void alloc() {
int x=1;
int y=2;
System.out.println("point.x=" + x + ";point.y=" + y);
}
这样可以减少堆内存的使用。
-XX:+EliminateAllocations
开启标量替换(默认打开),允许将对象打散分配在栈上。
小结
逃逸分析技术并不成熟,但它也是即时编译器优化技术中一个十分重要的手段。
无法保证进行逃逸分析的性能一定高于不进行逃逸分析的性能。