亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

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. 小結

本節主要講解了運行時數據區里邊的方法區,方法區是一塊共享內存區域,在運行時數據區占據著十分重要的位置。我們了解了方法區里邊存儲的數據類型,也了解到了方法區的作用,同時了解了方法區內存的版本變更,通篇皆為重點知識,學習者需要用心學習。