我们一般认为Java中创建的对象是放到堆里面的,但Java为了达到更高的性能要求,在高版本(此处指JDK8)中,有时会将对象直接生成在栈里面,比如我们常说的栈上分配。
我们都知道堆里面的对象回收,是需要等垃圾收集器处理的,但栈中的对象是随着方法调用结束而自行销毁,这样能达到更快释放的效果。那么栈上分配是怎样实现的呢?这里要说到两种机制:
一,逃逸分析
既然是逃逸,首先得有个“牢房”,在这里可以把每个方法当成一个“牢房”,每一个方法里的对象就是“囚犯”,那怎样逃呢?
当我们return 对象返回上级方法,或者是一个静态变量或常量,这样的对象在方法中就困不住,就会逃出去。那这种对象无法控制在方法内进行销毁,我们就称他们可以逃逸,反之则逃逸失败。
在JDK中逃逸分析默认是开启的,也可以使用JV参数关闭:
-XX:+DoEscapeAnalysis //开启
-XX:-DoEscapeAnalysis //关闭
我们可以思考下,无法逃逸的对象是不是就可以分配到栈上面随着栈的销毁而直接释放掉呢?
二,标量替换
标量是指无法再分解成更小粒度的数据,像数值类型。在 JIT 编译过程中,经过逃逸分析确定一个对象不会被其他线程或者方法访问,那么会将对象的创建替换成为多个成员变量的创建,称之为「标量替换」。
比如我们定义一个User类:
class User{
private String name;
private Integer age;
//省略getter和setter方法
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
当进行栈上分配时,不是将User对象存到栈中,而是将name和age分配进栈。
同样标量替换在JDK8中是默认开启的,也可以关闭:
-XX:+EliminateAllocations //开启
-XX:-EliminateAllocations //关闭
三,测试一下
void test(){
for(int i = 1;i<10000000;i++){
new User("先生"+1,i);
}
}
public static void main(String[] args) {
new Test().test();
}
//关闭逃逸分析和标题替换,将堆内存设为15M,因为创建的对象会直接放进堆中,所以会发生大量Full GC
-server -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
总结:栈上分配机制虽然能提升性能,但不是所有的对象都可以这样进行分配,它受制于我们栈内存的大小和我们创建的对象的大小,如果栈内存不足或对象过大,那还是老实老实存入堆里面吧。我是阿雷,一个划水多年的程序员。