当前位置: 首页 > 后端技术 > Java

面试官:Java中所有的对象都存储在堆中吗?你知道逃逸分析吗?

时间:2023-04-02 00:39:59 Java

面试官:Java虚拟机的内存分为哪些区域?我(笑):程序计数器,虚拟机栈,本地方法栈,堆,方法区面试官:对象一般存放在哪些区域?我:堆。面试官:对象是存储在堆上的吗?我是。采访者:你听说过逃逸分析吗?我(皱眉):是不是内存溢出了?面试官:没有。我(挠头):不太明白。采访者:今天的采访先到这里,回去等消息吧!然后就没有了,心不甘情不愿地开始查找相关资料。逃逸分析逃逸分析(EscapeAnalysis)是一种确定对象引用动态范围的分析方法。用人的话来说就是:分析在程序中哪里可以访问到对象的引用。当在方法中分配对象时,对该对象的引用可能会转义到其他执行线程,或返回给方法的调用者。如果一个方法分配了一个对象,返回的是该对象的引用指针,那么可能访问该对象的地方就无法确定,此时对象的引用就“逃逸”了。如果对象的引用保存在静态变量或其他数据结构中,由于静态变量可以在当前方法之外访问,此时对象的引用也“逃逸”了。逃逸分析确定所有可以访问对象引用的地方,以及对象引用的生命周期是否保证在当前进程或线程内。转义状态对象的转义状态一般分为三种:全局转义、参数转义和不转义。对GlobalEscape对象的引用会转义方法或线程。例如:将对象的引用赋值给一个静态变量,或者保存在一个已经转义的对象中,或者将对象的引用作为方法的返回值交给调用方法。比如饿了么的单例模式:packageone.more;publicfinalclassGlobalEscape{//实例对象赋值给静态变量,发生全局转义privatestaticGlobalEscapeinstance=newGlobalEscape();privateGlobalEscape(){}publicstaticGlobalEscapegetInstance(){返回实例;}}参数转义(ArgEscape)对象作为方法参数传递或被参数引用,但在调用过程中不会发生全局转义。这种状态是通过分析被调用方法的字节码来确定的。例如:packageone.more;publicclassArgEscape{classRectangle{privateintlength;私有整数宽度;publicRectangle(intlength,intwidth){this.length=length;this.width=宽度;}publicintgetArea(){返回this.length*this.width;}}publicintgetArea(intlength,intwidth){Rectanglerectangle=buildRectangle(length,width);返回矩形.getArea();}privateRectanglebuildRectangle(intlength,intwidth){Rectanglerectangle=newRectangle(length,width);//矩形对象有一个参数escapereturnrectangle;}}无转义(NoEscape)方法中的对象没有转义,也就是说该对象不能在堆上分配。例如:packageone.more;publicclassNoEscape{classRectangle{privateintlength;私有整数宽度;publicRectangle(intlength,intwidth){this.length=length;this.width=宽度;}publicintgetArea(){返回this.length*this.width;}}publicintgetArea(intlength,intwidth){//矩形对象不转义Rectanglerectangle=newRectangle(length,width);返回矩形.getArea();}}escape分析后的优化如果一个对象没有逃逸,或者只是参数逃逸,那么有可能对这个对象采取不同程度的优化,比如:栈上分配,标量替换,同步淘汰。堆栈分配如果一个对象不会从线程中逃逸,那么让该对象在堆栈上分配内存是个好主意。对象占用的内存空间可以随着栈帧弹出栈销毁分配。然后,对象会随着方法的结束而自动销毁,这样可以减少垃圾收集器运行的频率,垃圾收集的压力也会下降很多。标量替换(ScalarReplacement)标量(Scalar)是指一段数据不能被分解成更小的数据。Java虚拟机中的基本数据类型(数值类型如int、long、引用类型等)无法进一步分解,所以这些数据可以称为标量。相反,如果一段数据可以继续分解,则称为聚合,Java中的对象就是典型的聚合。如果一个Java对象被拆解,根据程序的访问情况,将其使用的成员变量恢复为基本类型,这个过程称为标量替换。如果一个对象没有逃逸,可以进行标量替换,那么对象的成员变量在栈上分配读写,不需要分配到堆上。标量替换可以看作是栈上分配的一种特殊情况。实现起来比较简单,但是对逃逸度的要求比较高。它不允许物体逃脱。同步消除(SynchronizationElimination)线程同步本身就是一个比较耗时的过程。如果一个对象没有脱离线程,不能被其他线程访问到,那么这个对象的读写肯定不会有竞争。对象上实现的同步可以安全地消除锁定操作。总结了这么多,可以发现并不是所有的对象都在堆上分配内存。因为通过逃逸分析后,可以对没有逃逸的对象进行标量替换。另外,由于复杂度等原因,HotSpot目前不支持栈分配的优化。最后,谢谢你这么帅,给我点赞和关注。