逃逸的条件
对象被赋值给堆中对象的字段和类的静态变量
对象被传进了不确定的代码中去运行
示例代码
public class EscapeTest {
public static Object globalVariableObject;
public Object instanceObject;
public void globalVariableEscape(){
globalVariableObject = new Object(); //静态变量,外部线程可见,发生逃逸
}
public void instanceObjectEscape(){
instanceObject = new Object(); //赋值给堆中实例字段,外部线程可见,发生逃逸
}
public Object returnObjectEscape(){
return new Object(); //返回实例,外部线程可见,发生逃逸
}
public void noEscape(){
synchronized (new Object()){
//仅创建线程可见,对象无逃逸
}
Object noEscape = new Object(); //仅创建线程可见,对象无逃逸
}
}
关于逃逸分析的优化
1.当判断出对象不发生逃逸时,编译器可以使用逃逸分析的结果作一些代码优化将堆分配转化为栈分配。如果某个对象在子程序中被分配,并且指向该对象的指针永远不会逃逸,该对象就可以在分配在栈上,而不是在堆上。在有垃圾收集的语言中,这种优化可以降低垃圾收集器运行的频率。
2.同步消除。如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步(锁消除)。
3.分离对象或标量替换。如果某个对象的访问方式不要求该对象是一个连续的内存结构,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
- 优化一
虚拟机配置参数:-XX:+PrintGC -Xms5M -Xmn5M -XX:+DoEscapeAnalysis
-XX:+DoEscapeAnalysis表示开启逃逸分析,JDK8是默认开启的
-XX:+PrintGC 表示打印GC信息
-Xms5M -Xmn5M 设置JVM内存大小是5M
public static void main(String[] args){
for(int i = 0; i < 5_000_000; i++){
createObject();
}
}
public static void createObject(){
new Object();
}
该运行结果没有发生GC
如果关闭逃逸分析,就会出现多次GC
这说明了JVM在逃逸分析之后,将对象分配在了方法createObject()方法栈上。方法栈上的对象在方法执行完之后,栈桢弹出,对象就会自动回收。这样的话就不需要等内存满时再触发内存回收。这样的好处是程序内存回收效率高,并且GC频率也会减少,程序的性能就提高了。
- 优化二
如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步,也就是锁的消除。
虚拟机配置参数:-XX:+PrintGC -Xms500M -Xmn500M -XX:+DoEscapeAnalysis。配置500M是保证不触发GC。
public static void main(String[] args){
long start = System.currentTimeMillis();
for(int i = 0; i < 5_000_000; i++){
createObject();
}
System.out.println("cost = " + (System.currentTimeMillis() - start) + "ms");
}
public static void createObject(){
synchronized (new Object()){
}
}
运行结果为6ms
如果关闭逃逸分析 -XX:+PrintGC -Xms500M -Xmn500M -XX:-DoEscapeAnalysis
运行结果为260ms
- 优化三
这个简单来说就是把对象分解成一个个基本类型,并且内存分配不再是分配在堆上,而是分配在栈上。这样的好处有,
一、减少内存使用,因为不用生成对象头。
二、程序内存回收效率高,并且GC频率也会减少,总的来说和上面优点一的效果差不多。