Java逃逸分析詳解及代碼示例
概念引入
我們都知道,Java 創(chuàng)建的對象都是被分配到堆內(nèi)存上,但是事實并不是這么絕對,通過對Java對象分配的過程分析,可以知道有兩個地方會導(dǎo)致Java中創(chuàng)建出來的對象并一定分別在所認為的堆上。這兩個點分別是Java中的逃逸分析和TLAB(Thread Local Allocation Buffer)線程私有的緩存區(qū)。
基本概念介紹
逃逸分析,是一種可以有效減少Java程序中同步負載和內(nèi)存堆分配壓力的跨函數(shù)全局數(shù)據(jù)流分析算法。通過逃逸分析,Java Hotspot編譯器能夠分析出一個新的對象的引用的使用范圍從而決定是否要將這個對象分配到堆上。
在計算機語言編譯器優(yōu)化原理中,逃逸分析是指分析指針動態(tài)范圍的方法,它同編譯器優(yōu)化原理的指針分析和外形分析相關(guān)聯(lián)。當變量(或者對象)在方法中分配后,其指針有可能被返回或者被全局引用,這樣就會被其他過程或者線程所引用,這種現(xiàn)象稱作指針(或者引用)的逃逸(Escape)。通俗點講,如果一個對象的指針被多個方法或者線程引用時,那么我們就稱這個對象的指針發(fā)生了逃逸。
Java在Java SE 6u23以及以后的版本中支持并默認開啟了逃逸分析的選項。Java的 HotSpot JIT編譯器,能夠在方法重載或者動態(tài)加載代碼的時候?qū)Υa進行逃逸分析,同時Java對象在堆上分配和內(nèi)置線程的特點使得逃逸分析成Java的重要功能。
代碼示例
package me.stormma.gc; /** * <p>Created on 2017/4/21.</p> * * @author stormma * * @title <p>逃逸分析</p> */ public class EscapeAnalysis { public static B b; /** * <p>全局變量賦值發(fā)生指針逃逸</p> */ public void globalVariablePointerEscape() { b = new B(); } /** * <p>方法返回引用,發(fā)生指針逃逸</p> * @return */ public B methodPointerEscape() { return new B(); } /** * <p>實例引用發(fā)生指針逃逸</p> */ public void instancePassPointerEscape() { methodPointerEscape().printClassName(this); } class B { public void printClassName(EscapeAnalysis clazz) { System.out.println(clazz.getClass().getName()); } } }
逃逸分析研究對于 java 編譯器有什么好處呢?我們知道 java 對象總是在堆中被分配的,因此 java 對象的創(chuàng)建和回收對系統(tǒng)的開銷是很大的。java 語言被批評的一個地方,也是認為 java 性能慢的一個原因就是 java不支持棧上分配對象。JDK6里的 Swing內(nèi)存和性能消耗的瓶頸就是由于 GC 來遍歷引用樹并回收內(nèi)存的,如果對象的數(shù)目比較多,將給 GC 帶來較大的壓力,也間接得影響了性能。減少臨時對象在堆內(nèi)分配的數(shù)量,無疑是最有效的優(yōu)化方法。java 中應(yīng)用里普遍存在一種場景,一般是在方法體內(nèi),聲明了一個局部變量,并且該變量在方法執(zhí)行生命周期內(nèi)未發(fā)生逃逸,按照 JVM內(nèi)存分配機制,首先會在堆內(nèi)存上創(chuàng)建類的實例(對象),然后將此對象的引用壓入調(diào)用棧,繼續(xù)執(zhí)行,這是 JVM優(yōu)化前的方式。當然,我們可以采用逃逸分析對 JVM 進行優(yōu)化。即針對棧的重新分配方式,首先我們需要分析并且找到未逃逸的變量,將該變量類的實例化內(nèi)存直接在棧里分配,無需進入堆,分配完成之后,繼續(xù)調(diào)用棧內(nèi)執(zhí)行,最后線程執(zhí)行結(jié)束,??臻g被回收,局部變量對象也被回收,通過這種方式的優(yōu)化,與優(yōu)化前的方案主要區(qū)別在于對象的存儲介質(zhì),優(yōu)化前是在堆中,而優(yōu)化后的是在棧中,從而減少了堆中臨時對象的分配(較耗時),從而優(yōu)化性能。
使用逃逸分析進行性能優(yōu)化(-XX:+DoEscapeAnalysis開啟逃逸分析)
public void method() { Test test = new Test(); //處理邏輯 ...... test = null; }
這段代碼,之所以可以在棧上進行內(nèi)存分配,是因為沒有發(fā)生指針逃逸,即是引用沒有暴露出這個方法體。
棧和堆內(nèi)存分配比較
package me.stormma.gc; /** * <p>Created on 2017/4/21.</p> * * @author stormma * @description: <p>內(nèi)存分配比較</p> */ public class EscapeAnalysisTest { public static void alloc() { byte[] b = new byte[2]; b[0] = 1; } public static void main(String[] args) { long b = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { alloc(); } long e = System.currentTimeMillis(); System.out.println(e - b); } }
JVM 參數(shù)為-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC, 運行結(jié)果
JVM 參數(shù)為-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC, 運行結(jié)果
性能測試
package me.stormma.gc; /** * <p>Created on 2017/4/21.</p> * * @author stormma * * @description: <p>利用逃逸分析進行性能優(yōu)化</p> */ public class EscapeAnalysisTest { private static class Foo { private int x; private static int counter; public Foo() { x = (++counter); } } public static void main(String[] args) { long start = System.nanoTime(); for (int i = 0; i < 1000 * 1000 * 10; ++i) { Foo foo = new Foo(); } long end = System.nanoTime(); System.out.println("Time cost is " + (end - start)); } }
使用逃逸分析優(yōu)化 JVM輸出結(jié)果( -server -XX:+DoEscapeAnalysis -XX:+PrintGC)
Time cost is 11012345
未使用逃逸分析優(yōu)化 JVM 輸出結(jié)果( -server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC)
[GC (Allocation Failure) 33280K->408K(125952K), 0.0010344 secs] [GC (Allocation Failure) 33688K->424K(125952K), 0.0009799 secs] [GC (Allocation Failure) 33704K->376K(125952K), 0.0007297 secs] [GC (Allocation Failure) 33656K->456K(159232K), 0.0014817 secs] Time cost is 68562263
分析結(jié)果,性能優(yōu)化1/6。
總結(jié)
以上就是本文關(guān)于Java逃逸分析詳解及代碼示例的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:Java實現(xiàn)生產(chǎn)者消費者問題與讀者寫者問題詳解、java算法實現(xiàn)紅黑樹完整代碼示例、Java編程接口調(diào)用的作用及代碼分享等,有什么問題可以隨時留言,小編會及時回復(fù)大家的。感謝朋友們對本站的支持。
相關(guān)文章
ThreadPoolExecutor中的submit()方法詳細講解
在使用線程池的時候,發(fā)現(xiàn)除了execute()方法可以執(zhí)行任務(wù)外,還發(fā)現(xiàn)有一個方法submit()可以執(zhí)行任務(wù),本文就詳細的介紹一下ThreadPoolExecutor中的submit()方法,具有一定的參考價值,感興趣的可以了解一下2022-04-04JPA?通過Specification如何實現(xiàn)復(fù)雜查詢
這篇文章主要介紹了JPA?通過Specification如何實現(xiàn)復(fù)雜查詢,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot的@Value給靜態(tài)變量注入application.properties屬性值
這篇文章主要介紹了SpringBoot的@Value給靜態(tài)變量注入application.properties屬性值,Spring是一個開源的框架,主要是用來簡化開發(fā)流程,通過IOC,依賴注入(DI)和面向接口實現(xiàn)松耦合,需要的朋友可以參考下2023-05-05SpringBoot整合Guava Cache實現(xiàn)全局緩存的示例代碼
這篇文章主要介紹了SpringBoot整合Guava Cache實現(xiàn)全局緩存,Guava Cache是Google Guava庫中的一個模塊,提供了基于內(nèi)存的本地緩存實現(xiàn),文中介紹了SpringBoot整合使用Guava Cache的具體步驟,需要的朋友可以參考下2024-03-03Spring中TransactionSynchronizationManager的使用詳解
這篇文章主要介紹了Spring中TransactionSynchronizationManager的使用詳解,TransactionSynchronizationManager是事務(wù)同步管理器,監(jiān)聽事務(wù)的操作,來實現(xiàn)在事務(wù)前后可以添加一些指定操作,需要的朋友可以參考下2023-09-09