对象一定分配在堆中吗?
JVM 通过逃逸分析, 那些逃不出方法的对象会在栈上分配.
什么是逃逸分析?
EscapeAnalysis, 逃逸分析, 指的是虚拟机在运行期通过计算分析将原本在堆上分配的对象改成在栈中分配, 这样的好处是栈上分配的对象随着线程的结束而自动销毁, 不依赖于 GC, 可以降低垃圾收集器运行的频率.
如何判定为逃逸?
JVM 判断新创建的对象是否逃逸的依据有两个:
对象被赋值给堆中对象的字段和类的静态变量
对象被传进了不确定的代码中去运行
如果满足了以上情况的任意一种, 那这个对象 JVM 就会判定为逃逸, 对以上两种情况举例, 样例来源于: https://zhuanlan.zhihu.com/p/59215831
- 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(){
- Object noEscape = new Object(); // 仅创建线程可见, 对象无逃逸
- }
- }
Java 的逃逸分析只能发生在即时编译 (JIT) 期, 为什么不能在静态编译 (javac) 中?
参考 R 大回答: https://www.zhihu.com/question/27963717
总结来说是可以发生在静态编译期的, 但是 Java 的分离编译和动态加载使得前期的静态编译的逃逸分析比较困难或收益较少, 所以目前 Java 的逃逸分析只发在 JIT 的即时编译中, 因为收集到足够的运行数据 JVM 可以更好的判断对象是否发生了逃逸.
JVM 开启逃逸分析以后的优势?
Java8 + 默认是开启的,
-XX:+DoEscapeAnalysis
栈上分配, 虚拟机参数:-XX:+PrintGC -Xms5M -Xmn5M -XX:+DoEscapeAnalysis
这种优化可以降低垃圾收集器运行的频率, 这样每当方法出栈, 对象内存随之释放.
- public static void main(String[] args) {
- for(int i = 0; i < 5000000; i++) {
- createObject();
- }
- }
- public static void createObject() {
- new Object();
- }
同步消除
如果发现某个对象只能从一个线程可访问, 那么在这个对象上的操作可以不需要同步.
标量替换
如果某个对象的访问方式不要求该对象是一个连续的内存结构, 那么对象的部分 (或全部) 可以不存储在内存, 而是存储在 CPU 寄存器中. 简单来说就是把对象分解成一个个基本类型, 并且内存分配不再是分配在堆上, 而是分配在栈上. 这样的好处有, 一, 减少内存使用, 因为不用生成对象头. 二, 程序内存回收效率高, 并且 GC 频率也会减少.
来源: https://segmentfault.com/a/1190000023475016