你們好,好幾天沒有更新了,今天的內容有點多,咱們詳細介紹下JVM內部結構圖,仍是和以前同樣,案例先行,方便你們理解記憶。java
/** * @author :jiaolian * @date :Created in 2021-03-10 21:28 * @description:helloworld測試jvm內存區域 * @modified By: * 公衆號:叫練 */ public class HelloWorldTest { public static void main(String[] args) { //新建HelloWorldTest對象; HelloWorldTest helloWorldTest = new HelloWorldTest(); //新建2個線程調用sayHello for (int i=0; i<2; i++) { new Thread(()->helloWorldTest.sayHello("world")).start(); } } /** * 對某人說hello * @param who */ public void sayHello(String who) { System.out.println(Thread.currentThread().getName()+"hello!"+who); } }
如上代碼:在主線程中for循環新建2個線程調用sayHello,最後兩個線程分別對世界問好!這段代碼比較好理解,就不貼輸出結果了。咱們編寫並運行了這段代碼,咱們主要看看這段代碼在JVM中是怎麼運做的。性能優化
首先,咱們編寫一個HelloWorldTest.Java文件,通過javac編譯會轉化成字節碼HelloWorldTest.class,爲何要轉化成字節碼呢?由於Java虛擬機能識別!最後由類加載子系統ClassLoader將字節碼裝載到內存。每塊內存各有本身的做用,最後由執行引擎來執行字節碼。下面咱們重點介紹下各塊內存發揮的做用!微信
方法區主要裝一些靜態信息,好比:類元數據,常量池,方法信息,類變量等。如上代碼HelloWorldTest.class是類元數據,sayHello,main都是方法信息等都是放在方法區存儲的。方法區中還須要注意兩點:多線程
堆中主要存放實例對象。你能夠這麼理解,只要看到用關鍵字new的對象,數據都放在堆中。如上代碼HelloWorldTest helloWorldTest = new HelloWorldTest();helloWorldTest是HelloWorldTest對象的引用,指向new出來的HelloWorldTest對象實例,helloWorldTest引用是放在棧中的,也叫局部變量(方法內申明的對象類型或普通類型),咱們簡單畫圖來表示下堆,棧,方法區關係。當JVM執行了HelloWorldTest helloWorldTest = new HelloWorldTest();這句話,JVM內存結構看起來是這樣的。若是指向對象引用消失,對象會被GC回收。jvm
在堆內存中,內存須要劃分紅兩塊區域,新生代和老年代。以下圖所示。ide
對堆分塊緣由是方便JVM自動處理垃圾回收。堆內存是GC回收的主要區域。函數
棧內存空間相對於堆空間比較小,也屬於線程私有,棧中主要是一堆棧幀,是先進後出的,理解起來棧幀對應就是一個方法,方法中包含局部變量,方法參數,還有方法出口,訪問常量指針,和異常信息表,其中異常信息表和常量指針信息咱們在方法體中可能看不出來,但經過工具Jclasslib工具類在反編譯class文件能夠體現出來,異常信息表能夠處理當程序執行報錯,會跳轉到具體哪行代碼執行,JVM中就是經過異常表反饋的。咱們仍是結合例子和圖來詳細分析下。當程序運行時,JVM中棧可能以下圖呈現狀態。工具
一個線程可能對應多個棧幀,棧幀都是從上往下壓入,先進後出,以下圖所示,在方法A中調用方法B,在方法B中調用C,在方法C中調用方法D,主線程對應棧幀的壓棧狀況,出棧順序是D->C->B->A,最終程序結束。另外還需注意:操做數棧的意思是存儲局部變量計算的中間結果,好比在方法A中定義int x = 1;在JVM中會將局部變量入操做數棧用來以後的計算。棧也是有空間大小的,若是棧太大,超過棧深度,會相似報錯,java.lang.OutOfMemoryError: Java stack space,最多見的例子就是遞歸了。你會寫demo測試遞歸例子嗎?性能
程序計數器也是線程獨享的,多線程執行程序依賴於CPU分配時間片執行,畫個簡單的圖,看看多線程怎麼利用CPU時間片的。以下圖,線程0和線程1分配cpu時間片交替執行程序,假設此時線程0先獲取到了時間片,時間片用完後CPU會將時間片再分配給線程1,線程1執行完畢後,此時,時間片又回到線程0來執行,那麼問題來了,線程0上次執行到哪兒了呢?具體是代碼的多少行了呢,該行代碼有沒有執行完畢?此時程序計數器就發揮做用了,程序計數器保存了線程的執行現場,方便下次恢復運行。這也是爲何程序計數器是線程獨享的緣由。測試
本地方法棧就不過多介紹了,和棧結構同樣,是一塊獨立的區域,只是對應的是native方法。
直接內存獨立於JVM內存以外的內存,能夠直接和NIO接口交互,NIO接口會頻繁操做內存,若是放在JVM管理,無疑會增長JVM開銷,因此單獨將這塊提出來,並且直接內存操做數據相比較JVM更快,顯而易見提高了程序性能。
咱們以前說過,只要是看到關鍵字new,對象分配確定在堆上,下面咱們來看一個案例。
/** * @author :jiaolian * @date :Created in 2021-03-10 16:10 * @description:逃逸分析測試 * @modified By: * 公衆號:叫練 */ public class EscapeTest { //private static Object object; public static void alloc() { //一個對象至關於16k大小,非逃逸對象 //object = new Object(); Object object = new Object(); } public static void main(String[] args) throws InterruptedException { //億次內存 long begin=System.currentTimeMillis(); for (int i=0; i<10000000; i++) { alloc(); } long end=System.currentTimeMillis(); System.out.println("time:"+(end-begin)); } }
如上代碼,咱們在主函數裏面經過for循環1億次來new Object,一個object爲16k,大體估算下有GB數據了,此時咱們手動配置JVM參數,-XX:+PrintGC -Xmx10M -XX:+DoEscapeAnalysis;設置打印GC信息,默認最大的堆內存是10M。
執行程序,打印結果以下圖所示。一共進行了3次GC,你可能有疑問?10M堆內存須要容納GB數據衝擊,怎麼也須要N次GC,爲何只有3次GC?若是設置-XX:-DoEscapeAnalysis關閉逃逸分析,GC可能會出現上千次。運行時間也從3毫秒增至1000毫秒以上。說明了非逃逸對象沒有新建的堆上,而是建在棧上了。這樣作的好處:從程序GC執行次數和執行時間上來看,程序運行效率提升了。
觀察咱們上述案例代碼中alloc()方法,方法中Object object = new Object();object是一個局部變量,每次新建後到下一次循環再新建,上一次新建的對象就會出棧,object引用指向的對象就會失效,失效的對象就會被GC回收了。開啓逃逸分析後,new Object()建立的對象就不在堆上分配空間了,而放到了棧上。這就是JVM經過逃逸分析對內存的優化。思考下,若是將private static Object object;註釋放開,object還會是非逃逸對象嗎?
注意:逃逸對象不能在棧上分配空間!
相信到這裏你已經對逃逸分析應該有一個比較清晰的認識了。
好了,寫的有點累了,寫的不全同時還有許多須要修正的地方,但願親們加以指正和點評,喜歡的請點贊加關注哦。點關注,不迷路,我是【叫練】公衆號,微信號【jiaolian123abc】邊叫邊練。