深入JVM內存區域

一、JVM內存區域劃分

 程序計數器

        程序計數器(Program Counter)是是一塊較小的區域,它的作用可以看做是當前線程所執行的字節碼的行號指示器。在虛擬機的模型裏,字節碼指示器就是通過改變程序計數器的值來指定下一條需要執行的指令。分支,循環等基礎功能就是依賴程序計數器來完成的。

        由於java虛擬機的多線程是通過輪流切換並分配處理器執行時間來完成,一個處理器同一時間只會執行一條線程中的指令。爲了線程恢復後能夠恢復正確的執行位置,每條線程都需要一個獨立的程序計數器,以確保線程之間互不影響。所以程序計數器是“線程私有”的內存。

        如果虛擬機正在執行的是一個Java方法,則計數器指定的是字節碼指令對應的地址,如果正在執行的是一個本地方法,則計數器指定問空undefined。程序計數器區域是Java虛擬機中唯一沒有定義OutOfMemory異常的區域。

Java虛擬機棧

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

        通常所說的虛擬機運行時分爲棧和堆,這裏的棧指的就是虛擬機棧或者說虛擬機棧中的局部變量表部分。

        局部變量表存放了編譯器可知的各種基本數據類型、對象引用和returnAddress類型(指向一條字節碼指令的地址)。局部變量表所需的內存空間在編譯器完成分配,當進入一個方法時這個方法需要在幀中分配多大的內存空間是完全確定的,運行期間不會改變局部變量表的大小。(64爲長度的long和double會佔用兩個局部變量空間,其他的數據類型佔用一個)

        Java虛擬機棧可能出現兩種類型的異常:1. 線程請求的棧深度大於虛擬機允許的棧深度,將拋出StackOverflowError。2.虛擬機棧空間可以動態擴展,當動態擴展是無法申請到足夠的空間時,拋出OutOfMemory異常。

 本地方法棧

        本地方法棧與Java棧的作用和原理非常相似。區別只不過是Java棧是爲執行Java方法服務的,而本地方法棧則是爲執行本地方法(Native Method)服務的。在JVM規範中,並沒有對本地方發展的具體實現方法以及數據結構作強制規定,虛擬機可以自由實現它。

     簡單地講,一個Native Method就是一個Java調用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實現由非java語言實現,比如C。這個特徵並非java所特有,很多其它的編程語言都有這一機制,比如在C++中,你可以用extern "C"告知C++編譯器去調用一個C的函數。 "A native method is a Java method whose implementation is provided by non-java code."

 

        堆是Java虛擬機所管理的內存中最大的一塊。堆是所有線程共享的一塊區域,在虛擬機啓動時創建。堆的唯一目的是存放對象實例,幾乎所有的對象實例都在這裏分配,不過隨着JIT編譯器的發展和逃逸技術的成熟,棧上分配和標量替換技術使得這種情況發生着微妙的變化,對上分配正變得不那麼絕對。

        Java堆是垃圾收集器管理的主要區域,所以也稱爲“GC堆”。由於現在的垃圾收集器基本上都是採用分代收集算法,所以Java堆還可細分爲:新生代和老生代。在細緻一點可分爲Eden空間,From Survivor空間,To Survivor空間。如果從內存分配的角度看,線程共享的Java堆可劃分出多個線程私有的分配緩衝區。不過無論如何劃分,都與存放內容無關,無論哪個區域,都是用來存放對象實例。細分的目的是爲了更好的回收內存或者更快的分配內存。

        Java堆可以是物理上不連續的空間,只要邏輯上連續即可,主流的虛擬機都是按照可擴展的方式來實現的。如果當前對中沒有內存完成對象實例的創建,並且不能在進行內存擴展,則會拋出OutOfMemory異常。

方法區

         方法區也是線程共享的區域,用於存儲已經被虛擬機加載的類信息,常量,靜態變量和即時編譯器(JIT)編譯後的代碼等數據。Java虛擬機把方法區描述爲堆的一個邏輯分區,不過方法區有一個別名Non-Heap(非堆),用於區別於Java堆區。

         Java虛擬機規範對這個區域的限制也非常寬鬆,除了可以是物理不連續的空間外,也允許固定大小和擴展性,還可以不實現垃圾收集。相對而言,垃圾收集行爲在這個區域是比較少出現的(所以常量和靜態變量的定義要多注意)。方法區的內存收集還是會出現,不過這個區域的內存收集主要是針對常量池的回收和對類型的卸載。

        一般來說方法區的內存回收比較難以令人滿意。當方法區無法滿足內存分配需求時將拋出OutOfMemoryError異常。

運行時常量池

       運行時常量池是方法區的一部分,Class文件中除了有類的版本,字段,方法,接口等信息以外,還有一項信息是常量池用於存儲編譯器生成的各種字面量和符號引用,這部分信息將在類加載後存放到方法區的運行時常量池中。Java虛擬機對類的每一部分(包括常量池)都有嚴格的規定,每個字節用於存儲哪種數據都必須有規範上的要求,這樣才能夠被虛擬機認可,裝載和執行。一般來說,除了保存Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。

        運行時常量池相對於Class文件常量池的另外一個重要特徵是具備動態性,Java虛擬機並不要求常量只能在編譯期產生,也就是並非預置入Class文件常量池的內容才能進入方法區的運行時常量池中,運行期間也可將新的常量放入常量池中。

       常量池是方法區的一部分,所以受到內存的限制,當無法申請到足夠內存時會拋出OutOfMemoryError異常

直接內存

        Java的NIO庫允許java程序使用直接內存。直接內存是在java堆外的、直接向系統申請的內存區間。通常,訪問直接內存的速度會優於java堆。因此,讀寫頻繁的場合可能會考慮使用直接內存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章