JVM 方法區
1. 前言
本節主要講解運行時數據區的方法區。本節主要知識點如下:
- 了解方法區的作用及意義,為本節的基礎知識;
- 了解方法區存放數據類型,為本節重點內容之一;
- 了解運行時常量池,我們在學習Class文件結構的時候,也學習過常量池結構,那么運行時常量池本節課程會進行講解;
- 了解方法區與堆內存結構的關系,以JDK 1.8 版本為分界線,進行對比講解,為本節重點內容之一。
2. 什么是方法區
定義:方法區,也稱非堆(Non-Heap),是一個被線程共享的內存區域。其中主要存儲加載的類字節碼、class/method/field 等元數據對象、static-final 常量、static 變量、JIT 編譯器編譯后的代碼等數據。另外,方法區包含了一個特殊的區域 “運行時常量池”。
Tips:對于運行時常量池,后文會有講解。
對于習慣在 HotSpot 虛擬機上開發和部署程序的開發者來說,很多人愿意把方法區稱為 “永久代”(Permanent Generation),本質上兩者并不等價,僅僅是因為 HotSpot 虛擬機的設計團隊選擇把 GC 分代收集擴展至方法區,或者說使用永久代來實現方法區而已。對于其他虛擬機(如 BEA JRockit、IBM J9 等)來說是不存在永久代的概念的。
3. 方法區存放的數據
在講解方法區內存放的數據之前,我們先通過示意圖來直觀的看下,方法區存放的數據與堆內存之間的關系。如下圖所示:
從圖中可以看到,方法區存放了 ClassLoader 對象的引用,也存放了一個到類對象的引用,這兩個引用的對象實例會存放到堆內存中。從上圖我們就可以簡單的了解到方法區存放的數據是什么,接下來,我們對存放的數據類型進行解釋。
- 類型全限定名:全限定名為 package 路徑與類名稱組合起來的路徑;
- 類型的直接超類的全限定名:父類或超類的全限定名;
- 類型是類類型還是接口類型:判定當前類是 Class 還是接口 Interface;
- 類型的訪問修飾符:判斷修飾符,如 pulic,private 等;
- 類型的常量池:這部分會在下文進行講解;
- 字段信息:類中字段的信息;
- 方法信息:類中方法的信息;
- 靜態變量:類中的靜態變量信息;
- 一個到類 ClassLoader 的引用:對 ClassLoader 的引用,這個引用指向對內存;
- 一個到 Class 類的引用:對對象實例的引用,這個引用指向對內存。
4. 運行時常量池
我們先來回顧下Class 文件結構中的常量池的相關知識。
Class 文件中的常量池:
在 Class 文件結構中,最頭的 4 個字節用于存儲 Megic Number,用于確定一個文件是否能被 JVM 接受,再接著 4 個字節用于存儲版本號,前 2 個字節存儲次版本號,后 2 個存儲主版本號,再接著是用于存放常量的常量池,由于常量的數量是不固定的,所以常量池的入口放置一個 u2 類型的數據 (constant_pool_count) 存儲常量池容量計數值。
常量池主要用于存放兩大類常量:字面量(Literal)和符號引用量(Symbolic References)。更加具體的知識,同學們可以翻看之前相關的小節內容。
運行時常量池:我們回到正題,來看下運行時常量池。
Tips:其實 Class 文件中的常量池與運行時常量池的關系非常容易理解,Class 文件中的常量池用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放。簡單總結來說,編譯器使用 Class 文件中的常量池,運行期使用運行時常量池。
運行時常量池相對于 Class 文件常量池的另外一個重要特征是具備動態性,Java 語言并不要求常量一定只有編譯期才能產生,也就是并非預置入 Class 文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是 String 類的 intern() 方法。
5. 常量池的優勢
常量池是為了避免頻繁的創建和銷毀對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。
- 節省內存空間:常量池中所有相同的字符串常量被合并,只占用一個空間。
- 節省運行時間:比較字符串時,
==
比equals ()
快。對于兩個引用變量,只用==
判斷引用是否相等,也就可以判斷實際值是否相等。
6. 方法區內存變更
方法區的實現,虛擬機規范中并未明確規定,目前有 2 種比較主流的實現方式:
HotSpot 虛擬機 1.8之前:在 JDK1.6 及之前版本,HotSpot 使用 “永久代(permanent generation)” 的概念作為實現,即將 GC 分代收集擴展至方法區。這種實現比較偷懶,可以不必為方法區編寫專門的內存管理,但帶來的后果是容易碰到內存溢出的問題(因為永久代有 - XX:MaxPermSize 的上限)。
在 JDK1.7,HotSpot 逐漸改變方法區的實現方式,如 1.7 版本移除了方法區中的字符串常量池,但為發生本質的變化。
HotSpot 虛擬機 1.8之后:1.8 版本中移除了方法區并使用 metaspace(元數據空間)作為替代實現。metaspace 占用系統內存,也就是說,只要不碰觸到系統內存上限,方法區會有足夠的內存空間。但這不意味著我們不對方法區進行限制,如果方法區無限膨脹,最終會導致系統崩潰。
7. 小結
本節主要講解了運行時數據區里邊的方法區,方法區是一塊共享內存區域,在運行時數據區占據著十分重要的位置。我們了解了方法區里邊存儲的數據類型,也了解到了方法區的作用,同時了解了方法區內存的版本變更,通篇皆為重點知識,學習者需要用心學習。