最簡單的JVM內存結構圖

JVM內存結構圖


image.png

你們好,好幾天沒有更新了,今天的內容有點多,咱們詳細介紹下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將字節碼裝載到內存。每塊內存各有本身的做用,最後由執行引擎來執行字節碼。下面咱們重點介紹下各塊內存發揮的做用!微信

image.png


方法區


方法區主要裝一些靜態信息,好比:類元數據,常量池,方法信息,類變量等。如上代碼HelloWorldTest.class是類元數據,sayHello,main都是方法信息等都是放在方法區存儲的。方法區中還須要注意兩點:多線程

  1. 若是方法區太大,超過設置,會報OutOfMemoryError:PermGen space錯誤。gclib工具能夠動態生成類測試該錯誤。
  2. 在JDK1.7之前,方法區叫永久代,而1.8以後叫元空間。緣由是JDK1.8爲了釋放管理壓力,把運行時常量池交給堆去管理。



堆中主要存放實例對象。你能夠這麼理解,只要看到用關鍵字new的對象,數據都放在堆中。如上代碼HelloWorldTest helloWorldTest = new HelloWorldTest();helloWorldTest是HelloWorldTest對象的引用,指向new出來的HelloWorldTest對象實例,helloWorldTest引用是放在棧中的,也叫局部變量方法內申明的對象類型或普通類型),咱們簡單畫圖來表示下堆,棧,方法區關係。當JVM執行了HelloWorldTest helloWorldTest = new HelloWorldTest();這句話,JVM內存結構看起來是這樣的。若是指向對象引用消失,對象會被GC回收。jvm

image.png

在堆內存中,內存須要劃分紅兩塊區域,新生代老年代。以下圖所示。ide

  1. 新生代:在堆內存中,新生代又分爲三塊,eden(伊甸園建立新生命,對應new對象),from,to,這三塊內存區域都屬於新生代,默認比例是8:1:1,每次new對象都會先存儲到eden中,若是eden區域內存滿了,會觸發monitor gc回收該區域,還未回收的對象會放入from或者to,from,to內存其中一塊是空的,方便對象在內存中整理標記,每GC一次,from,to兩塊空間對象每移動一次,還未回收的對象年紀也會增長1,到達必定年紀(默認是15歲),就會進入老年代了。
  2. 老年代:當老年代滿了,會觸發Full GC回收,若是系統太大,Full GC都回收不了,程序會出現相似java.lang.OutOfMemoryError: Java heap space,咱們能夠經過配置JVM參數:如-Xmx32m 設置最大堆內存爲32M。

對堆分塊緣由是方便JVM自動處理垃圾回收堆內存是GC回收的主要區域函數

image.png




棧內存空間相對於堆空間比較小,也屬於線程私有,棧中主要是一堆棧幀,是先進後出的,理解起來棧幀對應就是一個方法,方法中包含局部變量,方法參數,還有方法出口,訪問常量指針,和異常信息表,其中異常信息表和常量指針信息咱們在方法體中可能看不出來,但經過工具Jclasslib工具類在反編譯class文件能夠體現出來,異常信息表能夠處理當程序執行報錯,會跳轉到具體哪行代碼執行,JVM中就是經過異常表反饋的。咱們仍是結合例子和圖來詳細分析下。當程序運行時,JVM中棧可能以下圖呈現狀態。工具

image.png    

一個線程可能對應多個棧幀,棧幀都是從上往下壓入,先進後出,以下圖所示,在方法A中調用方法B,在方法B中調用C,在方法C中調用方法D,主線程對應棧幀的壓棧狀況,出棧順序是D->C->B->A,最終程序結束。另外還需注意:操做數棧的意思是存儲局部變量計算的中間結果,好比在方法A中定義int x = 1;在JVM中會將局部變量入操做數棧用來以後的計算。棧也是有空間大小的,若是棧太大,超過棧深度,會相似報錯,java.lang.OutOfMemoryError: Java stack space,最多見的例子就是遞歸了。你會寫demo測試遞歸例子嗎?性能


image.png


程序計數器


程序計數器也是線程獨享的,多線程執行程序依賴於CPU分配時間片執行,畫個簡單的圖,看看多線程怎麼利用CPU時間片的。以下圖,線程0和線程1分配cpu時間片交替執行程序,假設此時線程0先獲取到了時間片,時間片用完後CPU會將時間片再分配給線程1,線程1執行完畢後,此時,時間片又回到線程0來執行,那麼問題來了,線程0上次執行到哪兒了呢?具體是代碼的多少行了呢,該行代碼有沒有執行完畢?此時程序計數器就發揮做用了,程序計數器保存了線程的執行現場,方便下次恢復運行。這也是爲何程序計數器是線程獨享的緣由。測試

image.png



本地方法棧


本地方法棧就不過多介紹了,和棧結構同樣,是一塊獨立的區域,只是對應的是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。

  1. -XX:+PrintGC。表示控制檯打印GC信息。
  2. -Xmx10M。設置最大的堆內存爲10M。
  3. -XX:+DoEscapeAnalysis。 開啓逃逸分析(默認開啓)。

執行程序,打印結果以下圖所示。一共進行了3次GC,你可能有疑問10M堆內存須要容納GB數據衝擊,怎麼也須要N次GC,爲何只有3次GC?若是設置-XX:-DoEscapeAnalysis關閉逃逸分析,GC可能會出現上千次。運行時間也從3毫秒增至1000毫秒以上。說明了非逃逸對象沒有新建的堆上,而是建在棧上了。這樣作的好處:從程序GC執行次數和執行時間上來看,程序運行效率提升了。

image.png

  • 緣由分析:

觀察咱們上述案例代碼中alloc()方法,方法中Object object = new Object();object是一個局部變量,每次新建後到下一次循環再新建,上一次新建的對象就會出棧,object引用指向的對象就會失效,失效的對象就會被GC回收了。開啓逃逸分析後,new Object()建立的對象就不在堆上分配空間了,而放到了棧上。這就是JVM經過逃逸分析對內存的優化。思考下,若是將private static Object object;註釋放開,object還會是非逃逸對象嗎?

注意:逃逸對象不能在棧上分配空間!

相信到這裏你已經對逃逸分析應該有一個比較清晰的認識了。


總結


好了,寫的有點累了,寫的不全同時還有許多須要修正的地方,但願親們加以指正和點評,喜歡的請點贊加關注哦。點關注,不迷路,我是叫練公衆號,微信號【jiaolian123abc】邊叫邊練。

image.png

相關文章
相關標籤/搜索