閱讀筆記-Java內存區域劃分

對於Java開發人員來說,由jvm去管理內存的使用,我們不用關心內存的使用和釋放,但是一旦出現內存泄露和溢出,如果我們不瞭解JVM是如何管理內存的,我們將無從下手。

1.運行時數據區域

JVM會在執行java程序過程中把它所管理的內存劃分爲若干個不同的數據區域,這些區域都有各自的用途和銷燬及創建時間,有的隨着虛擬機進程啓動而存在,有些區域則依賴用戶線程的啓動和結束而建立銷燬。

(1)程序計數器

程序計數器是一塊較小的內存空間,它可以看做是當前線程所執行的字節碼行號的指示器,在虛擬機概念模型裏(僅僅是在概念模型裏,各種虛擬機會通過一些更高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支,循環,跳轉,異常處理,線程恢復等基礎功能都需要依賴這個計數器來完成。

由於Java虛擬機的多線程是通過線程輪流切換並分配處理器時間的方式來實現的,在任何一個確定的時刻,一個處理器的一個內核都只會執行一條線程中的指令,因此爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器。

這塊內存也是JVM規範中沒有規定任何OutOfMemoryError的情況。

(2)虛擬機棧

這塊內存也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的時候都會創建一個棧幀用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息,每個方法從調用到執行完的過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。

我們經常會把JVM內存劃分爲堆(Heap)和棧(Stack),這種分法比較粗糙,我們在這裏先說說棧,指的就是虛擬機棧。

局部變量表存放了編譯器可知的各種基本數據類型(8個基本類型),對象引用(引用類型,不等同於對象本身,只是一個指向對象地址的引用)和returnAddress類型(指向了一條字節碼指令的地址)。

其中64位長度的long和double類型的數據佔用2個局部變量控件,其餘數據只佔用1個。局部變量表所需的內存在編譯器完成分配,在方法運行期間不會改變局部變量表的大小,在虛擬機對這個區域規定了兩種異常:如果線程請求的棧深度大於虛擬機所允許的深度,拋出StackOverflowError異常;如果虛擬機棧可以動態擴展,如擴展時無法申請到足夠的內存,則會拋出OutOfMemoryError異常。

(3)本地方法棧

這個區域與虛擬機棧的作用非常相似,他們之間的區別是虛擬機棧爲虛擬機執行Java(字節碼)方法服務,本地方法棧則是調用虛擬機使用到的本地方法服務,這裏同樣也會拋出StackOverflowError和OutOfMemoryError異常。

(4)堆

對於很多應用來說,堆是JVM管理的內存中最大的一塊內存。這一塊內存是被所有線程所共享的,它在虛擬機啓動時創建。此區域唯一的目的就是存放對象實例,在Java虛擬機規範中的描述是:所有的對象實例及數組都要在堆上分配。但是隨着JIT編譯器的發展與逃逸分析技術的逐漸成熟,棧上分配,標量替換優化技術將會導致一些微妙的變化發生,所有對象都分配在堆上這句話就不那麼絕對了。

堆是GC(垃圾回收器)的主要管理區域,從內存回收的角度看,由於現在收集器基本都採用分代收集算法,所以Java堆還可以細分爲新生代和老年代;還可以再細一點:Eden空間,FormSurvivor空間,To Survivor空間等。堆的劃分目的主要是:與存放內容無關,爲了更好的回收內存,或者更快的分配內存。

我們可以通過jvisualvm工具(Java自帶分析工具)查看我們的堆內存使用情況(下一篇文章的主要講解內容)


(5)方法區

首先這塊內存也是所有線程共享的一塊區域,這裏存儲的內容就是我們熟知的類信息,常量,靜態變量,即時編譯後的代碼等。Java虛擬機規範把此區域描述爲堆的一個邏輯部分,但是他有個別名叫做:Non-heap(非堆)。

對於習慣了在HotSpot虛擬機上開發的開發者來說,我們更願意把它稱作“永久帶”(Permanent Generation),本質上兩者並不等價,但是HotSpot把GC的分代收集擴展至了方法區,這樣就省掉了單獨寫一個專門爲這部分的內存管理代碼。但是者更容易遇到內存溢出異常(OutOfMemoryException,永久帶有-XX:MaxPermSize的上限)。因此HotSpot虛擬機的官方在JDK1.7的HotSpot中,已經把原本放在永久帶中字符串常量池移出(採用Native Memory)來實現方法區的規劃。

GC在這一部分的內存比較少出現,主要是卸載類型和針對常量池的回收。當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。

(6)運行時常量池

運行時常量池是方法區的一部分,Class文件中除了有類的版本,字段,方法,接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各種字面量和符號引用,這部分內存將在類加載後進入方法區的運行時常量池中存放。

這塊內存並非預置入CLass文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,例如:String的intern方法,由於運行時常量池是方法區的一部分,所以也受到方法區內存的限制,當常量池無法再申請到內存時會拋出OutOfMemoryError異常。

(7)直接內存

直接內存並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,但是這塊內存也被頻繁使用,而且也可能導致OutOfMemoryError異常。

在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道和緩衝區的I/O方式,它可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。

本機直接內存顯然不會受到Java堆內存的限制,但是還是會受到本機總內存大小的限制。



發佈了42 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章