一、查看步驟
cmd執行命令:
java -XX:+PrintCommandLineFlags -version
輸出如下(舉例):
針對上述的-XX:UseParallelGC,這邊我們引用《深入理解Java虛擬機:JVM高級特性與最佳實踐》的介紹:
也就是說,打開此開關,使用的垃圾收集器是:新生代(Parallel Scavenge),老年代(Ps MarkSweep)組合。
二、更新於2020-07-14
本文自發表以來,閱讀量慢慢還在增加,一般來說,發表后,文章流量就穩定了,至於越來越高,這說明不少是搜索引擎搜索到來的。
受當時的見解所限,原來的分析,是有些問題的,是不充分的,因為jconsole/jvisualvm的面板,不一定對。
這邊有位同學,在評論里貼了一個鏈接,
https://www.zhihu.com/question/56344485
這里鏈接里,一樓有R大的回答,大家參考那個答案即可。
R大的意思是,自JDK7u4開始的JDK7u系列與JDK8系列,如果指定了:-XX:+UseParallelGC,則會默認開啟:
XX:+UseParallelOldGC 。
我這里自己也做了個實驗,jvm參數如下:
-Xms100M -Xmx100M -Xmn70M -XX:PretenureSizeThreshold=5M -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCCause -Xloggc:gc-old.log -verbose:gc
代碼demo很簡單,就是個普通的spring boot 的web程序:
@SpringBootApplication @RestController @Slf4j public class OrderServiceApplication { static Object object; static List<Object> list = new ArrayList<>(); public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } @RequestMapping("/addlist") public void lust(@RequestParam("value") Integer value) throws InterruptedException { byte[] bytes = new byte[value * 1024 * 1024]; list.add(bytes); } @RequestMapping("/clearlist") public void clearlist() throws InterruptedException { list.clear(); } }
pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>garbage-collection-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>garbage-collection-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
我們這里,總共的堆是100m,其中新生代(eden + s0/s1共70m,老年代30m):
然后我們不斷地調用
http://localhost:8082/addlist?value=10
很容易就能觸發old gc。
此時,查看我們的gc日志,下邊紅色我標出了ParOldGen字樣:
2020-07-13T23:59:26.190+0800: 287.077: [GC (Allocation Failure) --[PSYoungGen: 50176K->50176K(60928K)] 75372K->75380K(91648K), 0.0107778 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 2020-07-13T23:59:26.201+0800: 287.088: [Full GC (Ergonomics) [PSYoungGen: 50176K->49245K(60928K)] [ParOldGen: 25204K->25196K(30720K)] 75380K->74441K(91648K), [Metaspace: 35737K->35737K(1081344K)], 0.0611277 secs] [Times: user=0.08 sys=0.00, real=0.06 secs] 2020-07-13T23:59:30.196+0800: 291.082: [GC (Allocation Failure) --[PSYoungGen: 50176K->50176K(60928K)] 75372K->80492K(91648K), 0.0075458 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 2020-07-13T23:59:30.204+0800: 291.090: [Full GC (Ergonomics) [PSYoungGen: 50176K->44169K(60928K)] [ParOldGen: 30316K->30316K(30720K)] 80492K->74485K(91648K), [Metaspace: 35737K->35737K(1081344K)], 0.0816321 secs] [Times: user=0.09 sys=0.02, real=0.08 secs]
按照R大的說法,那就是啟用了Parallel Old這個老年代收集器。
所以,這里我們就可以得出結論:
在默認情況下,會開啟-XX:UseParallelGC參數,此時,新生代使用了Parallel New ,老年代使用了Parallel Old。
我這邊的版本是java 1.8:
C:\Windows\system32>java -version java version "1.8.0_11" Java(TM) SE Runtime Environment (build 1.8.0_11-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)
實驗二:
切換jvm參數為如下后(去掉了-XX:+UseParallelGC,增加了-server):
-Xms100M
-Xmx100M
-Xmn70M
-XX:PretenureSizeThreshold=5M
-server -XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
-Xloggc:gc-old.log
-verbose:gc
結果沒什么變化:
2020-07-14T00:18:57.133+0800: 15.023: [Full GC (Metadata GC Threshold) [PSYoungGen: 8679K->0K(58880K)] [ParOldGen: 8275K->12677K(30720K)] 16954K->12677K(89600K), [Metaspace: 33761K->33761K(1079296K)], 0.0882966 secs] [Times: user=0.19 sys=0.00, real=0.09 secs] 2020-07-14T00:19:48.649+0800: 66.537: [GC (Allocation Failure) --[PSYoungGen: 48890K->48890K(58880K)] 61568K->71808K(89600K), 0.0158096 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 2020-07-14T00:19:48.665+0800: 66.553: [Full GC (Ergonomics) [PSYoungGen: 48890K->12213K(58880K)] [ParOldGen: 22917K->21159K(30720K)] 71808K->33373K(89600K), [Metaspace: 35756K->35756K(1081344K)], 0.1872180 secs] [Times: user=0.48 sys=0.00, real=0.19 secs]
實驗三:
在使用以下參數時(使用了-XX:+UseSerialGC):
-Xms100M -Xmx100M -Xmn70M -XX:PretenureSizeThreshold=5M -client -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCCause -Xloggc:gc-old.log -verbose:gc
gc日志如下(gc日志開頭會輸出本次使用的jvm參數,大家可以注意:)
Java HotSpot(TM) 64-Bit Server VM (25.11-b03) for windows-amd64 JRE (1.8.0_11-b12), built on Jun 16 2014 20:57:32 by "java_re" with MS VC++ 10.0 (VS2010) Memory: 4k page, physical 12121744k(4890584k free), swap 14067940k(5380188k free) CommandLine flags: -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:MaxNewSize=73400320 -XX:NewSize=73400320
-XX:PretenureSizeThreshold=5242880 -XX:+PrintGC -XX:+PrintGCCause -XX:+PrintGCDateStamps
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 2020-07-14T00:26:57.301+0800: 1.888: [GC (Allocation Failure) 1.888: [DefNew: 57344K->6800K(64512K), 0.0209500 secs] 57344K->6800K(95232K), 0.0211331 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 2020-07-14T00:26:57.984+0800: 2.570: [GC (Allocation Failure) 2.570: [DefNew: 64144K->6072K(64512K), 0.0196770 secs] 64144K->8975K(95232K), 0.0197613 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 2020-07-14T00:26:58.270+0800: 2.856: [Full GC (Metadata GC Threshold) 2.856: [Tenured: 2903K->7142K(30720K), 0.0417688 secs] 34782K->7142K(95232K), [Metaspace: 20739K->20739K(1067008K)], 0.0418646 secs] [Times: user=0.03 sys=0.00, real=0.04 secs]
此時,新生代是DefNew,老年代是Tenured,明顯和前面使用了 -XX:+UseParallelGC時候不一樣。
-----------2020-07-14更新結束,以下為原答案,隨意看看就行。
二、驗證下,是不是那么回事吧
我用ide起了一個程序,然后在main中進行長時間睡眠。啟動時,設置其VM 參數如下:
然后用Jconsole連接該程序,切換到VM概要這個tab,注意下圖紅圈圈出來的地方:
結合第一步中的資料,很容易驗證,使用-XX:UseParallelGC的情況下,使用的垃圾收集器為:新生代(Ps Scanvenge),老年代(Ps MarkSweep,與Serial Old)。
三、Ps Scanvenge的簡要介紹
這邊附上我的簡單理解:該垃圾收集器適用於新生代,采用標記復制算法、多線程模型進行垃圾收集。
與其他新生代垃圾收集器的差別是,它更關注於吞吐量,而不是停頓時間。一般來說,需要與用戶交互的
程序更關注較短的停頓時間,而如果是需要達成盡量大的吞吐量的話,則該處理器會更加適合。
其通過-XX:UseAdaptiveSizePolicy參數,可以開啟其自動調節功能,適用於對垃圾收集器的調優不太了解的
用戶。
四、Serial Old的簡要介紹
我的理解:和其他老年代垃圾處理器一樣,都是使用的標記整理算法,(畢竟沒有靠山可以擔保,沒法復制,只能自己整理了,哎),
采用單線程處理模型。
五、Serial Old和Ps MarkSweep的區別
如上圖所示,也說了,在實際中,(正如第二節的截圖所示),實際應用中,大多使用的就是Ps MarkSweep。
Ps MarkSweep是以Serial Old為模板設計的,按照我們程序員的說法,估計是拷貝過來,改吧改吧出來的。
所以差不太多。