JVM內(nèi)存模型詳解

JVM內(nèi)存模型詳解

1.基本概念

JVM實際上是運行在一個具體操作系統(tǒng)上的程序進(jìn)程,對Java代碼而言,JVM就是操作系統(tǒng)的代理。

JVM內(nèi)存模型

如圖所示是JVM的內(nèi)存模型及數(shù)據(jù)交互。JVM的內(nèi)存模型依然是基于操作系統(tǒng)進(jìn)程空間的,不過是自己設(shè)計了一套內(nèi)存管理體系以支撐上層的Java代碼。

JVM的運行時內(nèi)存可以簡單的分為線程私有和公共內(nèi)存,線程私有部分包含程序計數(shù)器、Java棧、native方法棧。全局公共部分包含方法區(qū)、堆空間。

2.程序計數(shù)器

不止是JVM,操作系統(tǒng)本身就有程序計數(shù)器的概念,可以把JVM的程序計數(shù)器看做是對操作系統(tǒng)本身程序計數(shù)器的一種抽象。

程序計數(shù)器會記錄當(dāng)前線程下一條字節(jié)碼的位置。當(dāng)線程被掛起然后被恢復(fù)的時候,會根據(jù)程序計數(shù)器恢復(fù)線程的執(zhí)行邏輯。特別的,如果該線程正在執(zhí)行一個native方法,那么此時線程寄存器的值為”undefined”。

3.Java方法棧

Java棧也是線程私有的。每個方法在執(zhí)行的時候都會同時生成一個棧幀,用于存儲局部變量、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。方法執(zhí)行從開始到結(jié)束的過程,對應(yīng)了棧幀在虛擬機Java棧中從入棧到出棧的過程。

局部變量表中存儲了基本類型及引用類型(即對象的指針),其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot),其余的數(shù)據(jù)類型只占用1個。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。

4.native方法棧

native方法棧與Java方法棧類似,不過native方法棧是為虛擬機使用到的native方法服務(wù)的。

Java虛擬機規(guī)范對于這塊沒有強制規(guī)定,因此Sun HotSpot甚至直接就把native方法棧和Java方法棧合二為一。

5.Java堆

Java堆(Java Heap)是Java虛擬機所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存。這一點在Java虛擬機規(guī)范中的描述是:所有的對象實例以及數(shù)組都要在堆上分配。

Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱做“GC堆”。關(guān)于Java堆的詳細(xì)結(jié)構(gòu),也需要和GC機制一起來講才能比較清楚的理解,此處先跳過。

通過虛擬機啟動參數(shù),我們可以控制Java堆的最大內(nèi)存占用,如果超過最大內(nèi)存,會觸發(fā)OutOfMemory異常,進(jìn)而導(dǎo)致內(nèi)存申請失敗。如果出現(xiàn)這種異常,就要考慮是參數(shù)設(shè)置太小還是存在堆內(nèi)存泄露。

6.方法區(qū)

方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來。

對于習(xí)慣在HotSpot虛擬機上開發(fā)、部署程序的開發(fā)者來說,很多人都更愿意把方法區(qū)稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價,僅僅是因為HotSpot虛擬機的設(shè)計團(tuán)隊選擇把GC分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實現(xiàn)方法區(qū)而已,這樣HotSpot的垃圾收集器可以像管理Java堆一樣去管理這部分內(nèi)存。

但是使用永久代來實現(xiàn)方法區(qū),并不是一個好主意,因為這樣更加容易遇到內(nèi)存溢出問題,永久代的內(nèi)存分配一般比較小且固定,但是當(dāng)碰到String.intern這種運行時占用永久代內(nèi)存空間的方法的時候,很容易導(dǎo)致永久代內(nèi)存不夠用。因此在jdk1.7中,已經(jīng)把放在永久代中的字符串常量池移入到堆內(nèi)存當(dāng)中了。

7.運行時常量池

運行時常量池是方法區(qū)的一部分,存放了class文件在編譯期生成的各種字面量和符號引用。這部分內(nèi)容在類加載的時候放入運行時常量池中。

運行時常量池是動態(tài)變化的,不止存儲了class文件在編譯期生成的各種字面量,運行期間也可能放入新的常量,比如String類的intern方法。

8.直接內(nèi)存

直接內(nèi)存并不是虛擬機運行時區(qū)域的一部分,也不是Java虛擬機規(guī)范定義的內(nèi)存區(qū)域。JDK 1.4中新加入的NIO類,引入了一種基于通道和緩沖區(qū)的I/O方式,它可以使用native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作。這樣做是為了能在一些場景中顯著提高性能,因為避免了Java堆和native堆來回復(fù)制數(shù)據(jù)。

本機直接內(nèi)存的分配不受到Java堆的大小限制,但會受到物理內(nèi)存和操作系統(tǒng)的限制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容