「JVM」堆空间是分配对象存储的唯一选择嘛(逃逸分析)

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

堆空间是分配对象存储的唯一选择嘛(逃逸分析)

答:现有虚拟机是对象只有分配在堆上的。只是逃逸分析这个技术可以做到将对象分配到栈上

如果一个实例对象通过逃逸分析判断是只有这个方法使用的。那么这个实例对象就可以将内存分配到栈上。

逃逸分析条件:一个对象在方法中定义之后,只在方法内部使用,则认为没有发生逃逸

public void aaa(){
  A a=new A();
  ...
  ...
  a=null;
}
复制代码

在Java8及其以后,默认开启逃逸分析。在编译过程中JIT编译器根据分析结果,将对应的对象优化成栈上分配

/**
 * 逃逸分析
 *
 * 如何快速的判断是否发生了逃逸分析,大家就看new的对象实体是否有可能在方法外被调用。
 */
public class EscapeAnalysis {
​
    public EscapeAnalysis obj;
​
    /*
    方法返回EscapeAnalysis对象,发生逃逸
     */
    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(); //这个e对象,本身就是从外面的方法逃逸进来的
        //getInstance().xxx()同样会发生逃逸
    }
}
​
复制代码

逃逸分析的代码优化

  1. 栈上分配,就是没有发生逃逸的对象,在栈上进行内存的分配,这样在方法结束的时候出栈就释放了这个对象的内存空间

  2. 同步省略,逃逸分析的对象实例进行了栈上分配,那么这个对象就是线程私有的。那么如果原本对这个线程进行了加锁操作就是多余的。所以这个时候就不用进行线程数据同步了。

    JIT编译器分析该锁对象只有在该线程有效,不会影响到其他线程的数据。就不需要这个锁了,就进行了锁消除

    public void f() {
        Object hellis = new Object();
        synchronized(hellis) {
            System.out.println(hellis);
        }
    }
    这个锁本身针对的这个hellis就是一个会被逃逸分析认定为栈上分配的。所以这个线程的对象就不会出现线程共享的问题。所以这个锁就没有必要
    复制代码
  3. 分离对象/标量替换

    什么是标量:无法分解的数据,比如对象就是一个聚合量,但是一个int类型属性就是一个标量

    class Point {
        private int x;
        private int y;
    }
    private static void alloc() {
        Point point = new Point(1,2);
        System.out.println("point.x" + point.x + ";point.y" + point.y);
    }
    复制代码

    如果逃逸分析一个对象不会逃逸,那么就将这个对象(聚合量)分解成所有的标量。标量(基础数据类型)肯定是分配在栈上的。所以就不需要分配堆内存

猜你喜欢

转载自juejin.im/post/7087886704790470669