问题: 所有被new出来的实例都是存放在堆里的吗?
相关知识点: 堆和栈、标量替换、栈上分配、逃逸分析
思考:
首先,这样问了,答案肯定是不是所有new出来的实例都存放在堆里,不然下面没法继续问了。
如果有存在栈里的,和存在堆里有什么区别?
存在栈里有什么好处?
如果存在栈里好处这么多,那么为什么还要存在堆里?
栈里能不能存new出来的实例?
上图为一个User类,如果将new出来的实例 把int类型的id变量和string类型的name变量存放于栈的局部变量表中,login()存放于方法区。
把堆中的数据结构等价的平移替换到局部变量表中,这样的操作叫做标量替换。
在栈上分配空间存放实例叫做栈上分配。
因此,在栈上存放实例是可行的。
在栈上存放实例,好处是什么?
这里涉及到的就是堆和栈的区别,以及栈的特点。
好处就是一旦方法执行完毕,整个运行时栈帧就会出栈,局部变量自然也会出栈。实例如果存在于局部变量表中,局部变量表出栈后,实例也会被销毁。销毁过程不依赖GC,没有额外的开销。
实例出栈即销毁,不依赖GC,没有额外的开销。
什么情况不能把实例存在栈里
- 栈是线程私有的,当其他线程访问外部实例时,不能用栈来存储实例,会调用不到。
- 当存在逃逸分析情况时,是不能存在于局部变量表里的。
什么是逃逸分析?
参考逃逸分析解释原文链接
逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
public static StringBuffer craeteStringBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb;
}
StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
public static String createStringBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
如果将sb转为字符串,不存在逃逸分析的情况,即可满足栈上分配的要求。