https://zhuanlan.zhihu.com/p/272532249
今天繼續GC系列第三篇,熬夜不易,歡迎一鍵三連,給個鼓勵,不點贊也沒關係,我還可以,謝謝捧場[捂臉]。
常見垃圾回收器組合設定
在oracle官網上可以看到如何開啓使用指定垃圾回收的命令:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
垃圾回收器通常是組合使用的,我根據官網總結一下常見垃圾回收器的組合。
- -XX:+UseConcMarkSweepGC
啓用CMS垃圾收集器用於老年代。
當以吞吐量爲主的垃圾回收器(-XX:+UseParallelGC)無法滿足應用程序的延時要求時,Oracle建議使用的垃圾回收器是CMS或者G1(-XX:+UseG1GC)
默認情況下,此選項是禁用的,HotSpot VM會根據計算機的配置和JDK版本自動選擇收集器。
啓用此選項後,-XX:+UseParNewGC選項將自動開啓,並且不應禁用它,因爲在JDK 8中不推薦使用以下選項組合:-XX:+UseConcMarkSweepGC -XX:-UseParNewGC。
而CMS一旦老年代產生了很多內存碎片,它還會使用Serial Old進行清除。
所以,-XX:+UseConcMarkSweepGC=ParNew+CMS+Serial Old
- -XX:+UseG1GC
啓用G1垃圾回收器。
它適用於具有大量RAM的多處理器計算機。它能滿足GC暫停時間目標,同時保持良好的吞吐量。
建議將G1收集器用於需要大堆(大小約爲6 GB或更大)且GC延遲要求有限(穩定且可預測的暫停時間低於0.5秒)的應用程序。
- -XX:+UseParallelGC
使用並行清除垃圾收集器(也稱爲吞吐量收集器)來利用多個處理器來提高應用程序的性能。
默認情況下,此選項是禁用的,HotSpot根據計算機的配置和JDK版本自動選擇收集器。
如果啓用,則 -XX:+UseParallelOldGC選項也將自動啓用,除非明確禁用它。
所以,-XX:+UseParallelGC=Parallel Scavenge+Parallel Old
- -XX:+UseParallelOldGC
FullGC時使用ParallelOld。默認情況下,此選項是禁用的。
-XX:+UseParallelGC時,會自動-XX:+UseParallelOldGC。
- -XX:+UseParNewGC
支持在年輕代中使用並行線程進行收集。
默認情況下,此選項是禁用的。
當設置-XX:+UseConcMarkSweepGC選項時,它將自動啓用。
在JDK 8中,不建議使用-XX:+UseParNewGC選項而不使用-XX:+UseConcMarkSweepGC選項,也就是說ParNew和CMS最好同時啓用,不要強行開一個禁一個。
- -XX:+UseSerialGC
啓用Serial GC。
對於不需要垃圾回收具有任何特殊功能的小型和簡單的應用程序,這通常是最佳選擇。
默認情況下,此選項是禁用的,並且將根據計算機的配置和JVM的類型自動選擇收集器。
-XX:+UseSerialGC=Serial New+Serial Old。
查看JDK默認的垃圾回收器
使用如下命令
java -XX:+PrintCommandLineFlags -version
可以查看當前使用的JDK版本、虛擬機名稱及使用的垃圾回收器。
JDK 1.8 HotSpot VM默認GC是ParallelGC
JDK 11 HotSpot VM默認GC是G1
JVM常用命令參數
JVM命令可從如下網站查閱:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
HotSpot參數分類
- 標準 -開頭,所有的HotSpot都支持,比如
java -version
保證Java虛擬機(JVM)的所有實現都支持標準選項。它們用於常見操作,例如檢查JRE版本,設置類路徑,啓用詳細輸出等。
- 非標準 -X開頭,特定版本HotSpot支持特定命令
非標準選項是特定於Java HotSpot虛擬機的通用選項,因此不能保證所有JVM實現都支持它們,並且它們可能會發生變化。這些選項以-X開頭。
- 不穩定 -XX開頭,下個版本可能取消
這是高級選項,以-XX開頭,不建議隨意使用。這些是開發人員選項,用於調整Java HotSpot虛擬機操作的特定區域,這些區域通常具有特定的系統要求,並且可能需要對系統配置參數的特權訪問。也不能保證所有JVM實現都支持它們,並且它們可能會發生變化。
下面我們用一段程序,通過調JVM參數,使用JVM命令用不同的GC,看一下運行情況。
開始之前,先普及一下內存泄漏和內存溢出。
內存泄漏(memory leak):是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄漏似乎不會有大的影響,但內存泄漏堆積後的後果就是內存溢出。
內存溢出(out of memory):指程序申請內存時,沒有足夠的內存供申請者使用。比如程序不斷地產生對象到內存不夠了報錯OOM就是所謂的內存溢出。
再來看程序:
import java.util.LinkedList;
import java.util.List;
public class GCTest {
public static void main(String[] args) {
System.out.println("program start!");
List list = new LinkedList();
for (;;) {
byte[] b = new byte[1024 * 1024];
list.add(b);
}
}
}
很簡單的一段測試代碼,肯定會產生內存溢出,我們使用相關命令來觀察一下。
默認情況下,運行代碼:
[root@basic ~]# java GCTest
program start!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at GCTest.main(GCTest.java:9)
報了內存溢出。
如果我們想知道他的內存分配過程,可以在運行的時候添加相應的JVM參數,下面來找幾個常用的參數分別實驗一下。
- java -XX:+PrintCommandLineFlags GCTest
[root@basic ~]# java -XX:+PrintCommandLineFlags GCTest
-XX:InitialHeapSize=32449856 -XX:MaxHeapSize=519197696 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
program start!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at GCTest.main(GCTest.java:9)
-XX:+PrintCommandLineFlags:打印人類可識別的JVM相關信息,例如堆空間大小和選定的垃圾收集器。
InitialHeapSize:起始堆大小,根據內存計算出來
MaxHeapSize:最大堆大小
UseCompressedClassPointers:使用壓縮Class指針,比如Object.class,壓縮後-XX:+UseCompressedClassPointer的情況在內存中佔用4byte,不壓縮-XX:-UseCompressedClassPointer的情況爲8byte
UseCompressedOops:引用類型是否壓縮,-XX:+UseCompressedOops表示壓縮,壓縮後的引用類型在內存中佔4byte,不壓縮的情況佔用8byte。
- java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC GCTest
-Xmn 新生代大小
-Xms 最小堆大小
-Xmx 最大堆大小,最好和Xms設置一樣的值,防止堆彈來彈去,影響性能
-XX:+PrintGC 打印GC信息
執行如下:
[root@basic ~]# java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC GCTest
-XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
program start!
[GC (Allocation Failure) 7839K->7592K(39936K), 0.0274278 secs]
[GC (Allocation Failure) 14920K->14704K(39936K), 0.0091149 secs]
[GC (Allocation Failure) 22024K->21872K(39936K), 0.0124746 secs]
[GC (Allocation Failure) 29195K->29008K(39936K), 0.0150616 secs]
[Full GC (Ergonomics) 29008K->28929K(55296K), 0.0209756 secs]
[GC (Allocation Failure) 36254K->36225K(55296K), 0.0102666 secs]
[GC (Allocation Failure) 43543K->43393K(55808K), 0.0087281 secs]
[Full GC (Ergonomics) 43393K->43265K(60928K), 0.0104380 secs]
[GC (Allocation Failure) -- 51630K->58798K(60928K), 0.0100362 secs]
[Full GC (Ergonomics) 58798K->51458K(60928K), 0.0055668 secs]
[Full GC (Ergonomics) 58793K->58626K(60928K), 0.0045510 secs]
[Full GC (Allocation Failure) 58626K->58614K(60928K), 0.0116986 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at GCTest.main(GCTest.java:9)
[GC (Allocation Failure) 7839K->7592K(39936K), 0.0274278 secs]:新生代的GC,GC後堆內存佔用從7839K減少到7592K,堆大小爲39936K
可以看到,多次YGC後,回收不了了,就產生了FGC:
[Full GC (Ergonomics) 29008K->28929K(55296K), 0.0170423 secs]:產生了FullGC,GC後堆內存佔用從29008K較少到28929K
- java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails GCTest
-XX:+PrintGCDetails可以打印更詳細的GC信息:
[root@basic ~]# java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails GCTest
-XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
program start!
[GC (Allocation Failure) [PSYoungGen: 7839K->352K(9216K)] 7839K->7528K(39936K), 0.0074573 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 7680K->320K(9216K)] 14856K->14664K(39936K), 0.0070717 secs] [Times: user=0.00 sys=0.02, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 7640K->320K(9216K)] 21984K->21832K(39936K), 0.0064630 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 7643K->320K(9216K)] 29155K->29000K(39936K), 0.0070857 secs] [Times: user=0.01 sys=0.02, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 320K->0K(9216K)] [ParOldGen: 28680K->28929K(46080K)] 29000K->28929K(55296K), [Metaspace: 2514K->2514K(1056768K)], 0.0181374 secs] [Times: user=0.04 sys=0.01, real=0.02 secs]
[GC (Allocation Failure) [PSYoungGen: 7325K->128K(9216K)] 36254K->36225K(55296K), 0.0090166 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 7446K->128K(9728K)] 43543K->43393K(55808K), 0.0045376 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 128K->0K(9728K)] [ParOldGen: 43265K->43265K(51200K)] 43393K->43265K(60928K), [Metaspace: 2514K->2514K(1056768K)], 0.0076301 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 8365K->8365K(9728K)] 51630K->58798K(60928K), 0.0056167 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 8365K->1024K(9728K)] [ParOldGen: 50433K->50433K(51200K)] 58798K->51458K(60928K), [Metaspace: 2514K->2514K(1056768K)], 0.0113353 secs] [Times: user=0.02 sys=0.01, real=0.02 secs]
[Full GC (Ergonomics) [PSYoungGen: 8360K->8192K(9728K)] [ParOldGen: 50433K->50433K(51200K)] 58793K->58626K(60928K), [Metaspace: 2514K->2514K(1056768K)], 0.0056429 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 8192K->8192K(9728K)] [ParOldGen: 50433K->50422K(51200K)] 58626K->58614K(60928K), [Metaspace: 2514K->2514K(1056768K)], 0.0181221 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at GCTest.main(GCTest.java:9)
Heap
PSYoungGen total 9728K, used 8533K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 9216K, 92% used [0x00000000ff600000,0x00000000ffe55678,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 51200K, used 50422K [0x00000000fc400000, 0x00000000ff600000, 0x00000000ff600000)
object space 51200K, 98% used [0x00000000fc400000,0x00000000ff53d878,0x00000000ff600000)
Metaspace used 2545K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 275K, capacity 386K, committed 512K, reserved 1048576K
此外,關於GC信息的參數還有:
-XX:+PrintGCTimeStamps:在每個GC上打印時間戳。
-XX:+PrintGCApplicationConcurrentTime:打印自上一次暫停(例如GC暫停)以來經過的時間。
-XX:+PrintGCDateStamps:在每個GC上打印日期戳。
-XX:+PrintGCTaskTimeStamps:爲每個單獨的GC工作線程任務啓用時間戳打印。
- java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGC GCTest
這個主要是看切換成CMS垃圾回收器的GC過程信息:
[root@basic ~]# java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGC GCTest
-XX:InitialHeapSize=32449856 -XX:MaxHeapSize=519197696 -XX:MaxNewSize=172666880 -XX:MaxTenuringThreshold=6 -XX:OldPLABSize=16 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
program start!
[GC (Allocation Failure) 7865K->7452K(31680K), 0.0055673 secs]
[GC (Allocation Failure) 15815K->15634K(31680K), 0.0051261 secs]
[GC (CMS Initial Mark) 22965K(31680K), 0.0014019 secs]
[GC (Allocation Failure) 23989K->23956K(33736K), 0.0060109 secs]
[GC (Allocation Failure) 32314K->32191K(41960K), 0.0238551 secs]
[GC (Allocation Failure) 40551K->40280K(50184K), 0.0254390 secs]
[GC (Allocation Failure) 48641K->48486K(58408K), 0.0273632 secs]
[GC (Allocation Failure) 56847K->56655K(65796K), 0.0104898 secs]
[GC (CMS Final Remark) 65017K(65796K), 0.0020809 secs]
[GC (Allocation Failure) 65017K->64822K(74020K), 0.0080847 secs]
[GC (Allocation Failure) 73184K->72985K(116060K), 0.0048156 secs]
[GC (Allocation Failure) 81348K->81174K(116060K), 0.0156927 secs]
[GC (Allocation Failure) 89536K->89366K(116060K), 0.0164924 secs]
[GC (CMS Initial Mark) 91585K(116060K), 0.0005150 secs]
[GC (Allocation Failure) 97729K->97558K(116060K), 0.0200535 secs]
[GC (Allocation Failure) 105921K->105749K(116060K), 0.0195895 secs]
[GC (Allocation Failure) 114111K->113943K(123256K), 0.0185969 secs]
[GC (Allocation Failure) 122306K->122133K(131480K), 0.0174622 secs]
[GC (Allocation Failure) 130496K->130328K(139704K), 0.0168396 secs]
[GC (Allocation Failure) 138690K->138518K(147928K), 0.0222069 secs]
[GC (Allocation Failure) 146881K->146712K(156152K), 0.0189141 secs]
[GC (Allocation Failure) 155075K->154903K(164376K), 0.0212268 secs]
[GC (Allocation Failure) 163265K->163097K(172600K), 0.0184528 secs]
[GC (Allocation Failure) 171460K->171289K(180824K), 0.0230886 secs]
[GC (Allocation Failure) 179652K->179482K(189048K), 0.0197197 secs]
[GC (Allocation Failure) 187844K->187672K(197272K), 0.0211699 secs]
[GC (Allocation Failure) 196035K->195864K(205496K), 0.0219487 secs]
[GC (Allocation Failure) 204227K->204056K(213720K), 0.0221430 secs]
[GC (Allocation Failure) 212419K->212251K(221944K), 0.0237262 secs]
[GC (Allocation Failure) 220613K->220443K(230168K), 0.0274986 secs]
[GC (Allocation Failure) 228806K->228635K(238392K), 0.0253731 secs]
[GC (Allocation Failure) 236998K->236826K(246616K), 0.0186347 secs]
[GC (Allocation Failure) 245188K->245018K(254840K), 0.0188651 secs]
[GC (Allocation Failure) 253381K->253212K(263064K), 0.0232380 secs]
[GC (Allocation Failure) 261575K->261403K(271288K), 0.0192675 secs]
[GC (Allocation Failure) 269765K->269597K(279512K), 0.0250426 secs]
[GC (Allocation Failure) 277960K->277787K(287736K), 0.0230294 secs]
[GC (Allocation Failure) 286151K->285982K(295960K), 0.0252418 secs]
[GC (Allocation Failure) 294345K->294174K(304184K), 0.0195708 secs]
[GC (Allocation Failure) 302537K->302366K(312408K), 0.0252629 secs]
[GC (Allocation Failure) 310729K->310559K(320632K), 0.0241283 secs]
[GC (Allocation Failure) 318921K->318749K(328856K), 0.0235919 secs]
[GC (Allocation Failure) 327112K->326943K(336052K), 0.0204393 secs]
[GC (Allocation Failure) 335306K->335133K(344276K), 0.0242358 secs]
[Full GC (Allocation Failure) 343496K->343313K(344276K), 0.0948962 secs]
[GC (Allocation Failure) 344507K->347589K(349120K), 0.0145370 secs]
[Full GC (Allocation Failure) 347589K->344337K(349120K), 0.0053974 secs]
[GC (CMS Initial Mark) 345361K(491072K), 0.0031466 secs]
[GC (CMS Final Remark) 352315K(491072K), 0.0021340 secs]
[Full GC (Allocation Failure) 489533K->488726K(491072K), 0.0355269 secs]
[GC (CMS Initial Mark) 489750K(491072K), 0.0017936 secs]
[Full GC (Allocation Failure) 490513K->489750K(491072K), 0.0029711 secs]
[Full GC (Allocation Failure) 489750K->489739K(491072K), 0.0662137 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at GCTest.main(GCTest.java:9)
乍一看,和默認的ParallelGC日誌信息差不多,就是長了點,其實他就是多了一些CMS Initial Mark、CMS Final Remark等信息,
CMS初始標記、重新標記這些概念在上一篇已有介紹:
【GC系列】JVM堆內存分代模型及常見的垃圾回收器
通過這段日誌可以看到最後通過幾次FullGC都沒辦法清除,最終導致了OOM。
PS GC日誌詳解
先看這一句:
[GC (Allocation Failure) [PSYoungGen: 7839K->368K(9216K)] 7839K->7544K(39936K), 0.0072850 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]
該句GC日誌的含義:
GC:YGC,產生在年輕代(新生代)的GC。
Allocation Failure:產生GC的原因。
PSYoungGen:PS,新生代。
7839K->368K(9216K):垃圾回收之前是7839K,回收之後是368K(也就是說回收了7839-368的空間大小),9216K是整個年輕代的堆大小。
7839K->7544K(39936K):整個堆的空間大小變化。爲什麼總堆大小和年輕代的一樣都是7839K,沒有old區了嗎?原因就是全被年輕代佔用了。
0.0072850 secs:本次GC所耗時間
[Times: user=0.01 sys=0.02, real=0.01 secs]:user表示用戶態耗時,sys表示內核態耗時,real表示實際耗時
一旦產生了內存溢出,GC日誌把整個堆heap dump出來:
Heap
PSYoungGen total 9728K, used 8533K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 9216K, 92% used [0x00000000ff600000,0x00000000ffe55678,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 51200K, used 50422K [0x00000000fc400000, 0x00000000ff600000, 0x00000000ff600000)
object space 51200K, 98% used [0x00000000fc400000,0x00000000ff53d878,0x00000000ff600000)
Metaspace used 2545K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 275K, capacity 386K, committed 512K, reserved 1048576K
這個信息已經很明確了,比如PSYoungGen是新生代,eden是新生代中的eden區,from、to是兩個survivor區,ParOldGen是老年代,Metaspace是元空間。
主要看這幾個內存地址:
[0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
[起始地址, 使用空間結束地址, 整體空間結束地址)
eden space 9216K, 92% used [0x00000000ff600000,0x00000000ffe55678,0x00000000fff00000):
表示eden區的空間的內存地址從0x00000000ff600000到0x00000000fff00000,總共9216K,已使用了92%。
首發公衆號 行百里er ,歡迎老鐵們關注閱讀指正。也可訪問 GitHub github.com/xblzer/JavaJourney