JVM(一)---- 總結與專題目錄
JVM(二)----Java運行時數據區域
JVM(三)----垃圾收集算法及Safe Point介紹
JVM(四)----HotSpot的垃圾收集器與內存分配回收策略
JVM(五)----虛擬機類加載機制
本文結構如下:
1.運行時數據區域總覽
2.每一部分介紹
3.Hotspot永久代在1.8中移除的總結
首先理解一個概念,就是虛擬機實例。當啟動一個Java程序時,一個虛擬機實例也就誕生了。當該程序關閉退出,這個虛擬機實例也就隨之消亡。如果同一臺計算機上同時運行三個Java程序,將得到三個Java虛擬機實例。每個Java程序都運行于它自己的Java虛擬機實例中。
一、總覽
Java虛擬機所管理的內存將會包括一下幾個運行時數據區域:
注意:線程私有和共享的區域。
二、介紹每個部分
2.1程序計數器
- 作用:
記錄當前線程所執行到的字節碼的行號。字節碼解釋器工作的時候就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令。 - 意義:
JVM的多線程是通過線程輪流切換并分配處理器來實現的,對于我們來說的并行事實上一個處理器也只會執行一條線程中的指令。所以,為了保證各線程指令的安全順利執行,每條線程都有獨立的私有的程序計數器。 - 存儲內容
當線程中執行的是一個Java方法時,程序計數器中記錄的是正在執行的線程的虛擬機字節碼指令的地址。
當線程中執行的是一個本地方法時,程序計數器中的值為空。 - 可能出現異常
此內存區域是唯一一個在JVM上不會發生內存溢出異常(OutOfMemoryError)的區域。
2.2Java虛擬機棧
- 作用
描述Java方法執行的內存模型,也是線程私有的,生命周期與線程相同。每個方法在執行的同時都會開辟一段內存區域用于存放方法運行時所需的數據,稱為棧幀,一個棧幀包含如:局部變量表、操作數棧、動態鏈接、方法出口等信息。 - 意義
每個方法從調用到執行結束,就對應著一個棧幀在虛擬機棧中入棧和出棧的整個過程。 - 存儲內容
局部變量表(編譯期可知的各種基本數據類型、引用類型和指向一條字節碼指令的returnAddress類型)、操作數棧、動態鏈接、方法出口等信息。
值得注意的是:局部變量表所需的內存空間在編譯期間完成分配。在方法運行的階段是不會改變局部變量表的大小的。 - 可能出現的異常
如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常。
如果在動態擴展內存的時候無法申請到足夠的內存,就會拋出OutOfMemoryError異常。
2.3本地方法棧
- 作用
為JVM所調用到的Native方法(即本地方法)服務。 - 可能出現的異常
和虛擬機棧出現的異常很相像。也是StackOverflowError異常和OutOfMemoryError異常。
2.4 Java堆(Heap)
- 作用
所有線程共享一塊內存區域,在虛擬機啟動時創建,此內存區域的唯一目的就是存放對象實例。 - 意義
1、存儲對象實例,更好地分配內存。
2、垃圾回收(Garbage Collection)。堆是垃圾收集器管理的主要區域。更好地回收內存。 - 存儲內容
存放對象實例,幾乎所有的對象實例都在這里進行分配。堆可以處于物理上不連續的內存空間,只要邏輯上是連續的就可以。
值得注意的是:在JIT編譯器等技術的發展下,所有對象都在堆上進行分配已變得不那么絕對。 - 可能出現的異常
實現堆可以是固定大小的,也可以通過設置配置文件設置該為可擴展的。
如果堆上沒有內存進行分配,并無法進行擴展時,將會拋出OutOfMemoryError異常。
2.5方法區
- 作用
用于存儲運行時常量池、已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。 - 意義
對運行時常量池、常量、靜態變量等數據做出了規定。 - 存儲內容
運行時常量池(具有動態性)、已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。 - 可能出現的異常
當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
三、關于永久代在1.8中移除的總結
絕大部分 Java 程序員應該都見過 "java.lang.OutOfMemoryError: PermGen space "這個異常。這里的 “PermGen space”其實指的就是方法區。不過方法區和“PermGen space”又有著本質的區別。前者是 JVM 的規范,而后者則是 JVM 規范的一種實現,可以這么理解:方法區和永久帶的關系就像Java中接口和實現接口的類之間的關系一樣。并且只有 HotSpot 才有 “PermGen space”,而對于其他類型的虛擬機,如 JRockit(Oracle)、J9(IBM) 并沒有“PermGen space”。由于方法區主要存儲類的相關信息,所以對于動態生成類的情況比較容易出現永久代的內存溢出。
在 JDK 1.8 中, HotSpot 已經沒有 “PermGen space”這個區間了,取而代之是一個叫做 Metaspace(元空間) 的東西。下面我們就來看看 Metaspace 與 PermGen space 的區別。
其實,移除永久代的工作從JDK1.7就開始了。JDK1.7中,存儲在永久代的部分數據就已經轉移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并沒完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變量(class statics)轉移到了java heap。我們可以通過一段程序來比較 JDK 1.6 與 JDK 1.7及 JDK 1.8 的區別,以字符串常量為例:
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
這段程序不斷的生成新的字符串,并且通過intern方法將字符串放到字符串常量池中,這樣可以比較快速的消耗內存。我們通過 JDK 1.6、JDK 1.7 和 JDK 1.8 分別運行:
JDK 1.6 的運行結果:
JDK 1.7的運行結果:
JDK 1.8的運行結果:
從上述結果可以看出,JDK 1.6下,會出現“PermGen Space”的內存溢出,而在 JDK 1.7和 JDK 1.8 中,會出現堆內存溢出,并且 JDK 1.8中 PermSize 和 MaxPermGen 已經無效。因此,可以大致驗證 JDK 1.7 和 1.8 將字符串常量由永久代轉移到堆中,并且 JDK 1.8 中已經不存在永久代的結論。
元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制,但可以通過以下參數來指定元空間的大小:
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當提高該值。
-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。
除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導致的垃圾收集。
最后總結一下為什么移除永久帶而使用元空間的原因吧:
- 字符串存在永久代中,容易出現性能問題和內存溢出。
- 類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。
- 永久代會為 GC 帶來不必要的復雜度,并且回收效率偏低。
- Oracle 可能會將HotSpot 與 JRockit 合二為一。