• 逃逸分析


    【分析对象动态作用域】
    方法逃逸,线程逃逸。
     
    ——栈上分配:对象可以随着方法的结束而自动销毁。
    ——同步消除
    ——标量替换:将对象中使用到的成员变量恢复原始类型来使用。
    =======================================================================

    在编程语言的编译优化原理中,分析指针动态范围的方法称之为逃逸分析。它跟静态代码分析技术中的指针分析和外形分析类似。

    通俗一点讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸。

    而用来分析这种逃逸现象的方法,就称之为逃逸分析。

        1.    class A {  
        2.    public static B b;  
        3.       
        4.    public void globalVariablePointerEscape() { // 给全局变量赋值,发生逃逸  
        5.    b = new B();  
        6.    }  
        7.       
        8.    public B methodPointerEscape() { // 方法返回值,发生逃逸  
        9.    return new B();  
        10.    }  
        11.       
        12.    public void instancePassPointerEscape() {  
        13.    methodPointerEscape().printClassName(this); // 实例引用传递,发生逃逸  
        14.    }  
        15.    }  
        16.       
        17.    class B {  
        18.    public void printClassName(A a) {  
        19.    System.out.println(a.class.getName());  
        20.    }  
        21.    }  
     

    在这个例子中,一共举了3种常见的指针逃逸场景。分别是 全局变量赋值,方法返回值,实例引用传递。

     
    【逃逸分析优化   JVM   原理】

     我们知道java对象是在堆里分配的,在调用栈中,只保存了对象的指针。

    当对象不再使用后,需要依靠GC来遍历引用树并回收内存,如果对象数量较多,将给GC带来较大压力,也间接影响了应用的性能。减少临时对象在堆内分配的数量,无疑是最有效的优化方法。

    怎么减少临时对象在堆内的分配数量呢?不可能不实例化对象吧!

    场景介绍

    其实,在java应用里普遍存在一种场景。一般是在方法体内,声明了一个局部变量,且该变量在方法执行生命周期内未发生逃逸(在方法体内,未将引用暴露给外面)。

    按照JVM内存分配机制,首先会在堆里创建变量类的实例,然后将返回的对象指针压入调用栈,继续执行。

    这是优化前,JVM的处理方式。

    【逃逸分析优化 - 栈上分配

    优化原理:分析找到未逃逸的变量,将变量类的实例化内存直接在栈里分配(无需进入堆),分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,【局部的变量对象】也被回收。

    栈空间直接作为临时对象的存储介质。从而减少了临时对象在堆内的分配数量

    逃逸分析的原理很简单,但JVM在应用过程中,还是有诸多考虑。

    比如,逃逸分析不能在静态编译时进行,必须在JIT里完成。原因是,与java的动态性有冲突。因为你可以在运行时,通过动态代理改变一个类的行为,此时,逃逸分析是无法得知类已经变化了。

    【逃逸分析另一个重要的优化 - 同步消除

    如果你定义的类的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。

    性能测试

    来自 http://blog.uncommons.org/ 性能测试结果。

    测试场景1:

    生成几百万个随机数,然后做一些少量运算。

    VM 参数: -server

    95 秒

    VM 参数: -server -XX:+DoEscapeAnalysis

    73 秒

    性能提高: 23%

    测试场景2:

    非负矩阵分解算法。

    VM 参数: -server

    22.6 秒

    VM 参数: -server -XX:+DoEscapeAnalysis

    20.8 秒

    性能提升: 8%

    JVM   中启用逃逸分析    DoEscapeAnalysis

    安装jdk1.6.0_14,运行java时传递jvm参数  -XX:+DoEscapeAnalysis

    逃逸分析还能用于以下优化场景,但在JVM中未知使用。

    1,标量替换(Scalar Replacement)

    2,减小竞争检测范围

    3,基于区域的内存分配


    整理自 周志明《深入JVM》
    1, 是JVM优化技术,它不是直接优化手段,而是为其它优化手段提供依据。
     
    2,逃逸分析主要就是分析对象的动态作用域。
     
    3,逃逸有两种:方法逃逸和线程逃逸。
     
               方法逃逸   (对象逃出当前方法):
     
                    当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其它方法中。
     
               线程逃逸   ((对象逃出当前线程):
     
                    这个对象甚至可能被其它线程访问到,例如赋值给类变量或可以在其它线程中访问的实例变量
     
    4,   如果不存在逃逸,则可以对这个变量进行优化
            1,栈上分配。
     
                    在一般应用中,不会逃逸的局部对象占比很大,如果使用栈上分配,那大量对象会随着方法结束而自动销毁,垃圾回收系统压力就小很多。
     
            2,同步消除
     
                    线程同步本身比较耗时,如果确定一个变量不会逃逸出线程,无法被其它线程访问到,那这个变量的读写就不会存在竞争,对这个变量的同步措施   可以清除。
     
            3,   【标量替换】
     
                    1, 标量就是不可分割的量,java中基本数据类型,reference类型都是标量。相对的一个数据可以继续分解,它就是聚合量(aggregate)。
     
                    2, 如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换。
     
                    3, 如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那么程序真正执行的时候   将可能不创建这个对象,而改为直接在>栈上创建若干个成员变量   。
     
    5,逃逸分析还不成熟。
     
            1,不能保证逃逸分析的性能收益必定高于它的消耗。
     
                    判断一个对象是否逃逸耗时长,如果分析完发现没有几个不逃逸的对象,那时间就白白浪费了。
     
            2,基于逃逸分析的优化手段不成熟,如上面提到的栈上分配,由于hotspot目前的实现方式导致栈上分配实现起来复杂。
     
     
    6,相关JVM参数   
            -XX:+DoEscapeAnalysis 开启逃逸分析   
            -XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。   
            -XX:+EliminateAllocations 开启标量替换   
            -XX:+EliminateLocks 开启同步消除   
            -XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况。
     
  • 相关阅读:
    前端备战21秋招之操作系统,线程/进程/死锁
    前端备战秋招之计算机网络,这一篇足矣
    VS Code项目中共享自定义的代码片段方案
    eslint插件开发教程
    2020前端春招经验分享,从面试小白到老油条的蜕变
    使用nodejs从控制台读入内容
    js实现展开多级数组
    js使用typeof与instanceof相结合编写一个判断常见变量类型的函数
    07-数据结构
    06-流程控制
  • 原文地址:https://www.cnblogs.com/lsx1993/p/4620017.html
Copyright © 2020-2023  润新知