Java虛擬機器--方法區(執行時常量池)

可樂丶發表於2018-07-15

文章引用:

深入理解Java虛擬機器
https://blog.csdn.net/huangfan322/article/details/53220169

一 方法區描述

方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已經被虛擬機器載入的類資訊/常量//靜態資訊/即時編譯器編譯後的程式碼等資料.雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻又一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來.

對於習慣在HotSpot虛擬機器上開發,部署程式的開發者來說,很多人都更願意把方法區成為”永久代”(Permanent Generation),本質上兩者並不相等,僅僅是因為HotSpot虛擬機器的設計團隊選擇把GC分代收集擴充套件至方法區,或者說使用永久代來實現方法區而已,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分內容,能夠省去專門為方法區編寫記憶體管理程式碼的工作.對於其他虛擬機器(如BEA JRockit/ IBM J9)來說是不存在永久代的概念的.原則上,如何實現方法區屬於虛擬機器實現細節,不受虛擬機器規範約束,但是使用永久代來實現方法區,現在看來並不是一個好主意,因為這樣更容易遇到記憶體洩漏問題(永久代有-XX:MaxPermSize的上限,J9和JRockit只要沒有觸碰到程式可用記憶體的上限,例如:32位作業系統中的4GB,就不會出現問題),而且有極少的方法(例如String.intern())會因為這個原因導致不同虛擬機器下有不同的表現.因此,對於HotSpot虛擬機器,根據官方釋出的路線圖資訊,現在也有放棄永久代並逐步採用Native Memory來實現方法區的規劃了,在目前已經發布的JDK1.7的HotSpot中,已經把原本放在永久代的字串常量池移出.

Java虛擬機器規範對方法區的限制非常寬鬆,除了和Java堆一樣不需要連續的記憶體和可以選擇固定大小或者可擴充套件外,還可以選擇不實現垃圾收集.相對而言,垃圾收集行為在這個區域是比較少出現的,但是並非資料進入了方法區就如同進入永久代的名字一樣”永久”存在了.這區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝,一般來說,這個區域的回收”成績”比較難以令人滿意,尤其是對型別的解除安裝,條件相當苛刻,但是這部分割槽域的回收確實是存在必要的.在Sun公司的BUG列表裡,曾出現過的若干個嚴重的BUG就是由於低版本的HotSpot虛擬機器對此區域未完全回收而導致記憶體洩漏.

根據Java虛擬機器規範的規定,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常.


這裡寫圖片描述

1、型別資訊

  • 型別的全限定名
  • 超類的全限定名
  • 直接超介面的全限定名
  • 型別標誌(該類是類型別還是介面型別)
  • 類的訪問描述符(public、private、default、abstract、final、static)

2、型別的常量池

存放該型別所用到的常量的有序集合,包括直接常量(如字串、整數、浮點數的常量)和對其他型別、欄位、方法的符號引用。常量池中每一個儲存的常量都有一個索引,就像陣列中的欄位一樣。因為常量池中儲存中所有型別使用到的型別、欄位、方法的字元引用,所以它也是動態連線的主要物件(在動態連結中起到核心作用)。

3、欄位資訊(該類宣告的所有欄位)

  • 欄位修飾符(public、protect、private、default)
  • 欄位的型別
  • 欄位名稱

4、方法資訊

方法資訊中包含類的所有方法,每個方法包含以下資訊:

  • 方法修飾符
  • 方法返回型別
  • 方法名
  • 方法引數個數、型別、順序等
  • 方法位元組碼
  • 運算元棧和該方法在棧幀中的區域性變數區大小
  • 異常表

5、類變數(靜態變數)

指該類所有物件共享的變數,即使沒有任何例項物件時,也可以訪問的類變數。它們與類進行繫結。

6、 指向類載入器的引用

每一個被JVM載入的型別,都儲存這個類載入器的引用,類載入器動態連結時會用到。

7、指向Class例項的引用

類載入的過程中,虛擬機器會建立該型別的Class例項,方法區中必須儲存對該物件的引用。通過Class.forName(String className)來查詢獲得該例項的引用,然後建立該類的物件。

8、方法表

為了提高訪問效率,JVM可能會對每個裝載的非抽象類,都建立一個陣列,陣列的每個元素是例項可能呼叫的方法的直接引用,包括父類中繼承過來的方法。這個表在抽象類或者介面中是沒有的,類似C++虛擬函式表vtbl。

執行時常量池(Runtime Constant Pool)

執行時常量池(Runtime Constant Pool)是方法區的一部分.Class檔案中除了有類的版本/欄位/方法/介面等描述資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將類在載入後進入方法區的執行時常量池中存放.

Java虛擬機器對Class檔案每一部分(自然也包括常量池)的格式都有嚴格規定,每一個位元組用於儲存哪種資料都必須符合規範上的要求才會被虛擬機器任何/裝載和執行,但是對於執行時常量池,Java虛擬機器規範沒有做任何細節的要求,不用的提供上實現的虛擬機器可以按照自己的需要來實現這個記憶體區域.不過,一般來說,除了儲存Class檔案中描述的符號引用外,還會把翻譯出來的直接引用也儲存在執行時常量池中.

執行時常量池相對於Class檔案常量池的另外一個重要特徵就是具備動態性,Java語言並不要求常量一定只有在編譯期才能產生,也就是並非預置入Class檔案中常量池的內容才能進入方法區執行時常量池,執行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法.其中intern()方法描述如下:

這裡寫圖片描述

既然執行時常量池是方法區的一部分,自然受到方法區記憶體的限制,當常量池無法再申請到記憶體時會丟擲OutOfMemoryError異常.

二 方法區特徵

1 執行緒共享

方法區是堆的一個邏輯部分,因此和堆一樣,都是執行緒共享的.整個虛擬機器只有一個方法區.

2 永久代

方法區中的資訊一般需要長期存在,而且它又是堆的邏輯分割槽,因此用堆的劃分方法,我們把方法區稱之為老年代.

3 記憶體回收效率低

方法區中的資訊一般需要長期存在,回收一遍記憶體之後可能只有少量資訊無效.對方法區的記憶體回收的主要目標是:對常量池的回收和對型別的解除安裝.

4 Java虛擬機器堆方法區的要求比較寬鬆

和堆一樣,允許固定大小,也允許可擴充套件的大小,還允許不識閒垃圾回收.

相關文章