Java虛擬機內存模型及垃圾回收監控調優
如果你想理解Java垃圾回收如果工作,那么理解JVM的內存模型就顯的非常重要。今天我們就來看看JVM內存的各不同部分及如果監控和實現垃圾回收調優。
JVM內存模型
正如你上圖所看到的,JVM內存可以划分為不同的部分,廣義上,JVM堆內存可以划分為兩部分:年輕代和老年代(Young Generation and Old Generation)
年輕代(Young Generation)
年輕代用於存放由new所生成的對象。當年輕代空間滿時,垃圾回收就會執行。這個垃圾回收我們稱之為 最小化垃圾回收(Minor GC)。年輕代又可以分為三分部: 一個Eden內存區(Eden Memory)和兩個Survivor 內存區(Survivor Memory)。
關於年輕代的重點:
- 大多數新創建的對象都存放在Eden內存區。
- 當Eden區存滿對象時,最小化GC將會被執行,所有存活的對象被移到其中一個Survivor區。
- 最小化GC同時也會檢查存活的對象並將它們移到另一個Survivor區,所以在一段時間,其中一個Survivor區總是空的。
- 存活的對象經過多次GC后,將會被移到老年代(Old Generation)通常年輕代轉化多長時間變為老年代都會設置一個閥值。
老年代(Old Generation)
老年代內存包含那些經過多次最小化GC且存活的對象。 通常當老年代內存滿時垃圾回收會被執行。我們稱之為大GC (Major GC),通常這個過程會花費很長的時間。
Stop the World Event
所有的垃圾回收都是“阻塞”事件(“Stop the World” events) ,因為所有應用程序線程必須停止,直到垃圾回收操作完成之后才能繼續。
由於年輕代總是保存着短期存活的對象,最小化GC非常快,應用程序也不會受到影響。然而大GC(Major GC)由於它要檢查所有存活的對象,所以需要花費很長的時間。 大GC會在垃圾回收期間使得你的應用程序沒有任何響應,應最大化的減少此類GC。如果你有一個即時響應應用程序且總是有很多的大GC在執行。你會發現存在超時錯誤。
垃圾回器掃行的時間依賴於GC所使用的策略。這就是為什么對於那些高響應用程序來說,GC的監控與調優顯得非常必要。
永久代(Permanent Generation)
永久代或持久代包含JVM用來描述應用程序中使用的類和方法的元數據。注意 永久代不是JAVA堆內存的一部分。
JVM在運行期間依據應用程序所使用的類來存入永久代,同時也包含Java SE庫類各方法。 永久代中的對象通過全GC(Full GC)來進行垃圾回收。
方法區Method Area
方法區是永久代的一部分,用於存儲類結構(運行時的常量和靜態變量)及方法和構造的代碼。
運行時常量池Runtime Constant Pool
運行時常量池是類中常量池在編譯期的表示,它包含類的運行時常量,靜態方法,它是方法區的一部分。
Java棧內存Java Stack Memory
Java 棧內存用於執行線程。包含方法短期存活的特定值及方法中所涉及的指向堆中其它對象的引用。
Java 堆內存Switches(Java Heap Memory Switches)
提供了大量的內存Switch,我們可以用來設置內存大小及它們的比率。一此常用的內存Switches如下。
VM Switch | VM Switch 描述 |
---|---|
-Xms | 設置JVM啟動時堆初始值。 |
-Xmx | 設置堆的最大值 |
-Xmn | 設置年輕代的大小 |
-XX:PermGen | 設置永久代內存的初始值 |
-XX:MaxPermGen | 設置永久代內存的最大值 |
-XX:SurvivorRatio | 用於提供Eden區和Survivor區的比例, 例如,如果年輕代的大小為 10M,VM switch is -XX:SurvivorRatio=2 那么 5 M 分配給 Eden 區,每一個Survivor 區為 2.5 M .默認比例大小為 8。 |
-XX:NewRatio | 老年代/新生代的比例. 默認值是 2. |
大多數情況下,上述選項是非常有用的。如果你也想試試其它參數可以查看 JVM 官方主頁
JAVA GC
Java GC是從內存中標記、移除非可用對象並釋放所分配的空間用於后續創建對像的過程。Java編程語言最大的功能之一就是自動垃圾回收。不像其它類似C的編程語言,它們的內存分配與回收可以人為操作。
GC是后台執行用於檢查內存中的所有對象並找出未被任務程序所引用的對象。所有這些未引用的對象將被刪除,同時空間也會被回收用於分配給其它對象。
關於標記、刪除方法有兩個問題:
1.由於新創建的對象將會變成不可用,所有效率上不是很高。
2.多次GC后可用的對象很能在后續的GC后仍然可用。
上述方法的不足在於Java的GC是分代的,在堆內存中分年輕代和老年代。 前面我已經解釋過基於最小化GC(Minor GC)和大GC(Major GC)對象是如果被掃描並從一個分代區移到別一個分區。
Java GC的類型
在應用程序中我們可以使用如下五種類型的GC。我們可以使用JVM相關設置為應用程序開啟GC策略,現在我們就來具體的看看.
- 串行GC (Serial GC -XX:+UseSerialGC): 串行GC為年輕代和老年代GC提供簡單的標記-清除-壓縮方法。串行GC在獨立應用程序運行的少量CPU的客戶機器上非常有效。對於那些低內存占用的應用程序非常有利。
- 並行GC (Parallel GC -XX:+UseParallelGC): 並行GC中大部分實現與串行GC相同,除了年輕代的垃圾回收使用N個線程,N是系統CPU內核的數量。我們可以通過 JVM 的 -XX:ParallelGCThreads=n 選項來控制線程的數量。並行的垃圾回收器因為使用多CPU來加速GC的實現所以也稱為並行收集器(throughput collector).並行GC使用單線程來處理老年代的垃圾回收.
- 並行Old GC (Parallel Old GC -XX:+UseParallelOldGC):此類型的GC中年輕代和老年代GC都使用多線程進行。其它與並行GC相同,
- 並發標記清除收集器(Concurrent Mark Sweep (CMS) Collector) (-XX:+UseConcMarkSweepGC): CMS收集器是並發短暫停收集器。它為老年代進行垃圾回收。CMS收集器 通過與應用程序線程並發執行垃圾回收 從而縮短因GC而出現的暫停時間。CMS收集器在年輕代使用與並行收集器相同的算法。這類垃圾收集器適合不能容忍長時間暫停的響應程序。我們可以通過JVM中的 -XX:ParallelCMSThreads=n 來設置CMS收集器中的線程數。
- G1 垃圾收集器G1 Garbage Collector (-XX:+UseG1GC) Java 7中可以使用G1 垃圾收集器。它的最終目標是替換掉CMS收集器。G1收集器是一個並行、並發、增量壓縮、低暫停的垃圾收集器。
G1垃圾收集器不同於其它收集器,它沒有年輕代、老年代空間,它將堆空間划分成多個相同大小的堆區域(heap regions),當調用一次垃圾回收,它首先收集實時性不是很高的區域的數據,因此稱為”Garbage First”。有關詳細的可參考我的另外一篇Blog:Java垃圾收集器之--Garbage-First Collector 。
JAVA 垃圾收集監控
我們可以使用Java命令行和UI工具來監控應用程序的垃圾收集活動。下面的例子中,我使用Java SE Downloads 中一個演示程序。
如果你想使用同樣的程序,前往 Java SE Downloads 頁面下載JDK 7 and JavaFX Demos and Samples。 我使用的和序示例是Java2Demo.jar 可以在jdk1.7.0_55/demo/jfc/Java2D 目錄中找到。當然了這步可選。你可以選用任何Java程序執行GC監控命令。
我使用的啟動演示程序的命令是:
1
|
pankaj@Pankaj:~
/Downloads/jdk1
.7.0_55
/demo/jfc/Java2D
$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
|
JSTAT
我們可以使用 jstat 命令行工具來監控JVM內存和GC活動。標准的JDK中含有此命令。因此可以直接使用。
運行 jstat 之前你需要知道程序的進程ID號。你可以運行 ps -eaf | grep java 命令來獲取。
1
2
3
|
pankaj@Pankaj:~$
ps -eaf | grep Java2Demo.jar
501 9582 11579 0 9:48PM ttys000 0:21.66
/usr/bin/java
-Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
501 14073 14045 0 9:48PM ttys002 0:00.00
grep
Java2Demo.jar
|
我的Java程序的進程ID號為 9582.現在我們可以如下執行jstat 命令。
1 pankaj@Pankaj:~$ jstat -gc 9582 1000 2 S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 3 1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 4 1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 5 1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 6 1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 7 1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 8 1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656 9 1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
jstat命令的最后一個參數是每次輸出的時間間隔,因此它會每隔一秒打印內存及垃圾回收數據。下面詳細看看每列。
-
- S0C and S1C: 這列顯示當前 Survivor0 and Survivor1 區域的大小(KB)。
- S0U and S1U: 這列顯示 Survivor0 and Survivor1 區域的使用情況(KB). 其中一個Survivor區域總是空的。
- EC and EU: 這列顯示Eden區的當前大小及使用情況(KB). 注意 EU 的大小逐漸增加,當達到EC大小, 最小化 GC 被調用 ,EU 的大小減小。
- OC and OU: 這列顯示了老年代的當前大小及使用情況(KB)。
- PC and PU: 這列顯示了 持久化代(Perm Gen) 的當前大小及使用情況(KB)。
- YGC and YGCT: YGC 列顯示年輕代發生GC事件的數量。 YGCT 列顯示年輕代發生GC操作累計時間. 注意這兩列值都在增加與EU值減少是在同一行。這主要是最小化GC的原因。
- FGC and FGCT: FGC 列顯示了FUll GC發生的數量. FGCT 列顯示了FULL GC操作的累計時間. 注意Full GC 所用的時間相比年輕代GC所用的時間要大的多。
- GCT: 這列顯示了GC 操作總累計時間。 注意它是 YGCT 和 FGCT 值的總和。
jstat的優點在於它可以在無GUI的遠程服務器上執行。注意S0C, S1C 和EC之和為 10M ,與我們通過JVM選項 -Xmn10m 設置的值一樣。
Java VisualVM with Visual GC
如果你想在GUI下查看內存及GC操作。 那么你可以使用 jvisualvm 工具。Java VisualVM 也同樣包含在JDK中,你不需要單獨下載。
只需要運行在終端上執行jvisualvm 命令來啟動Java VisualVM程序。一旦啟動,你需要通過Tools--》Plugins選項安裝Visual GC 插件,正如下圖所示。
Visual GC 安裝完畢后,左邊列中打開程序前往Visual GC 部分。你將會得到如下圖所示的JVM內存及GC截圖。
Java GC 調優是提高應用程序吞吐量的最后選擇,只有當你發現長時間的GC導致性能下降而產生應用程序超時。
你會在日志中看到java.lang.OutOfMemoryError: PermGen space的錯誤信息。然后可以嘗試監控並通過使用JVM 選項 -XX:PermGen and -XX:MaxPermGen 來增加Perm Gen內存空間。你或許也可以嘗試使用-XX:+CMSClassUnloadingEnabled 來檢查使用CMS垃圾收集的性能如何?
如果你發現大量的FUll GC操作,你可以試着增加老年代內存空間。
總之GC調優需要花費大量的精力和時間,這里絕沒有什么硬性或者快速的規則。你需要嘗試不同的選項,比較他們,並找對你應用程序來說最好的那個。
這就是所有有關Java內存模型和垃圾回收。我希望這有助於你理解JVM內存模型及垃圾回收的過程。謝謝。
原文鏈接: JournalDev 翻譯: TonySpark
譯文鏈接: http://www.cnblogs.com/tonyspark/p/3731696.html
[ 轉載請保留原文出處、譯者和譯文鏈接。]