堆是分配对象的唯一选择么?
在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:
逃逸分析
逃逸分析举例
public void my_method() {
V v = new V();
// use v
// ....
v = null;
}
public static StringBuffer createStringBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
//sb可以被外部方法引用
return sb;
}
public static String createStringBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
/**
* 逃逸分析
*
* 如何快速的判断是否发生了逃逸分析,就看new的对象实体是否有可能在方法外被调用。
*/
public class EscapeAnalysis {
public EscapeAnalysis obj;
/*
方法返回EscapeAnalysis对象,发生逃逸,同上sb
*/
public EscapeAnalysis getInstance(){
return obj == null? new EscapeAnalysis() : obj;
}
/*
为成员属性赋值,发生逃逸
*/
public void setObj(){
this.obj = new EscapeAnalysis();
}
//思考:如果当前的obj引用声明为static的?仍然会发生逃逸。
/*
对象的作用域仅在当前方法中有效,没有发生逃逸
*/
public void useEscapeAnalysis(){
EscapeAnalysis e = new EscapeAnalysis();
}
/*
引用成员变量的值,发生逃逸
*/
public void useEscapeAnalysis1(){
EscapeAnalysis e = getInstance();
//getInstance().xxx()同样会发生逃逸
}
}
逃逸分析参数设置
总结
开发中能使用局部变量的,就不要在方法外定义。
使用逃逸分析,编译器可以对代码做如下优化:
栈上分配
栈上分配举例:
/**
* * 栈上分配测试
* * -Xmx128m -Xms128m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
* */
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");
// 为了方便查看堆内存中对象个数,线程sleep
try {
Thread.sleep(1000000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
private static void alloc() {
User user = new User();//未发生逃逸
}
static class User {
}
}
-Xmx128m -Xms128m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
开启逃逸分析的情况:
-Xmx128m -Xms128m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
同步省略(同步消除)
示例代码:
public void f() {
Object hollis = new Object();
synchronized(hollis) {
System.out.println(hollis);
}
}
代码中对hollis这个对象加锁,但是hollis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉,优化成:
public void f() {
Object hellis = new Object();
System.out.println(hellis);
}
字节码分析上面第一个程序发现:
0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init>>
7 astore_1
8 aload_1
9 dup
10 astore_2
11 monitorenter
12 getstatic #3 <java/lang/System.out>
15 aload_1
16 invokevirtual #4 <java/io/PrintStream.println>
19 aload_2
20 monitorexit
21 goto 29 (+8)
24 astore_3
25 aload_2
26 monitorexit
27 aload_3
28 athrow
29 return
注意:字节码文件中并没有进行优化,可以看到加锁和释放锁的操作依然存在,同步省略操作是在解释运行时发生的!
标量替换
分离对象或标量替换:
标量替换代码示例:
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);
}
class Point {
private int x;
private int y;
}
以上代码,经过标量替换后,就会变成:
private static void alloc() {
int x = 1;
int y = 2;
System.out.println("point.x = " + x + "; point.y=" + y);
}
标量替换参数设置:
参数 -XX:+ElimilnateAllocations:开启了标量替换(默认打开),允许将对象打散分配在栈上。
代码示例:
未开启标量替换:
-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
-server -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
这里设置参数如下:
逃逸分析的不足
堆是分配对象的唯一选择么? (不是!)