目錄
一、JVM?Optimization
1、Shenandoah
Shenandoah的使用方法
2、ZGC
ZGC的版本更迭
ZGC的使用方法
ZGC的參數設置
3、JMH測試GC性能
一、JVM?Optimization
1、Shenandoah
????????Shenandoah是由Red Hat開發的一款低延遲的垃圾收集器,Shenandoah并發執行大部分GC工作,包括并發的整理,堆大小對STW的時間基本沒有影響。
Shenandoah的使用方法
1、下載:Shenandoah只包含在OpenJDK中,默認不包含在內,需要單獨構建,可以直接下載構建好的。
Shenandoah下載地址
下載選擇方式如下:?
1、{aarch64,arm32-hflt,mipsel,mips64el,ppc64le,s390x,x86_32,x86_64}:架構,linux中使用arch命令選擇對應的架構。
[root@localhost ~]# arch
x86_64
2、{server,zero}:虛擬機類型,選擇server,包含所有GC功能
3、{release,fastdebug,Slowdebug,optimization}:不同的優化級別,選擇release,性能最高。
4、{gcc*glibc*,msvc*}:編譯期版本,選擇較高的版本性能好一些,如果兼容性有問題,(無法啟動)選擇較低的版本。--glibc版本選擇
[root@localhost ~]# ldd --version
ldd (GNU libc) 2.17
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
[root@localhost ~]#
centos版本低。重新安裝ubuntu操作系統22.4.3版本
root@admin:~# ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.5) 2.35
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
root@admin:~#
我這里選擇的版本:openjdk-jdk-shenandoah-linux-x86_64-server-release-gcc10-glibc2.31.tar.xz
root@admin:/usr/java# java -version
openjdk version "22-testing" 2024-03-19
OpenJDK Runtime Environment (build 22-testing-builds.shipilev.net-openjdk-jdk-shenandoah-b171-20231209)
OpenJDK 64-Bit Server VM (build 22-testing-builds.shipilev.net-openjdk-jdk-shenandoah-b171-20231209, mixed mode, sharing)
2、配置。將OpenJDK配置到環境變量中,使用java -version進行測試。打印出如下內容代表成功。
3、添加參數,運行Java程序
-XX:+UseShenandoahGC 開啟Shenandoah GC
-Xlog:gc 打印GC日志
2、ZGC
ZGC文章
The Z Garbage Collector (ZGC) is a scalable low latency garbage collector. ZGC performs
all expensive work concurrently, without stopping the execution of application threads
for more than a millisecond. It is suitable for applications which require low latency.
Pause times are independent of the heap size that is being used. ZGC works well with
heap sizes from a few hundred megabytes to 16TB. ZGC was initially introduced as an experimental feature in JDK 11, and was declared
Production Ready in JDK 15. In JDK 21 was reimplemented to support generations.Z垃圾收集器(ZGC)是一種可擴展的低延遲垃圾收集器。ZGC執行所有昂貴的工作都是并發的,而不會停止
應用程序線程的執行持續超過一毫秒。它適用于需要低延遲的應用程序。暫停時間與正在使用的堆大小無關。
ZGC與堆大小從幾百兆字節到16TB。
ZGC最初是作為JDK 11中的一個實驗特性引入的,并聲明JDK 15中的生產就緒。JDK中重新實現了21,以支持幾代人。
? ? ? ? ZGC(Z Garbage Collector)是一款基于Region內存布局的,暫時不設分代的,使用了讀屏障、顏色指針等技術來實現可并發的,采用標記-整理算法的,以低延遲為首要目標的一款垃圾收集器。
????????ZGC是一種可擴展的低延遲垃圾回收器。ZGC在垃圾回收過程中,STW的時間不會超過一毫秒,適合需要低延遲的應用。支持幾百兆到16TB的堆大小,堆大小對STW的時間基本沒有影響。
????????ZGC降低了停頓時間,能降低接口的最大耗時,提升用戶體驗。但是吞吐量不佳,所以如果Java服務比較關注QPS(每秒的查詢次數),那么G1是比較不錯的選擇。
內存布局
????????跟G1類似,ZGC的堆內存也是基于Region來分布。不同的是,ZGC的Region支持動態地創建和銷毀,并且Region的大小不是固定的,包括三種類型的Region:
Smail Region:2MB,主要用于放置小于256KB的小對象。
Medium Region:32MB,主要用于放置大于等于256KB小于4MB的對象。
Large Region:N *?2MB,這個類型的Region是可以動態變化的,不過必須是2MB的整數倍,最小支持4MB,每個Large Region只放置一個大對象,并且是不會被重分配的。
顏色指針
顏色指針,如下圖所示,是ZGC的核心設計之一。以前的垃圾回收器的GC信息都保存在對象頭中,而ZGC的GC信息保存在指針中。
每個對象有一個64位指針,這64位被分為:
????????18位:預留給以后使用。
????????1位:Finalizable標識。
????????1位:Remapped標識,是否進入了重分配集(即被移動過)。
????????1位:Marked1標識,簡稱M1。
????????1位:Marked0標識,和上面的Marked1都是用于輔助GC。簡稱M0。
????????42位:對象的地址(所以它可以支持2^42=4T內存)。
垃圾收集過程
大概分為4個階段:并發標記,并發預備重分配,并發重分配,并發重映射。
????????并發標記(Concurrent Mark):與G1一樣,并發標記是遍歷對象,做可達性分析的階段。前后也要經過類似G1的初始標記、最終標記的短暫停頓。與G1不同的是,ZGC的標記是在指針上而不是在對象上進行的,標記階段會更新顏色指針的M0/M1標志位。
????????并發預備重分配(Concurrent Prepare for Relocate):這個階段根據特定的查詢條件統計出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocate Set)。重分配集與G1收集器的回收集(Collection Set)還是有區別的,ZGC劃分Region的目的并非為了像G1那樣做收益優先的增量回收。相反,ZGC每次回收都會掃描所有的Region。
????????并發重分配(Concurrent Relocate):該階段是ZGC執行過程中的核心階段,這個過程要把重分配集中的存活對象復制到新的Region上,并為重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關系。得益于顏色指針的支持,ZGC收集器能僅從引用上就明確得知一個對象是否處于重分配分配集之中,如果用戶線程此時并發訪問了位于重分配集中的對象,這次訪問將被讀屏障所截獲,然后立即根據Region上的轉發表記錄將訪問轉發到新復制的對象上,并同時修正更新該引用的值,使其直接指向新對象,ZGC的這種行為稱作指針的“自愈”能力。
????????并發重映射(Concurrent Remap):該階段所做的主要是修正整個堆中指向重分配集中舊對象的所有引用。但是ZGC的并發重映射并不是必須要“迫切”去完成的任務。因為即使是舊引用,它也是可以自愈的,最多只是第一次使用時多一次轉發和修正操作。因此,ZGC很巧妙的將并發重映射階段要做的工作,合并到了下一次GC中的并發標記階段里去完成,反正它們都是要遍歷對象,這樣就節省了一次遍歷的開銷。一旦所有的指針被修正之后,原來記錄新舊關系的轉發表就可以釋放掉了。
ZGC的版本更迭
? ? ? ? 2018年JDK11,實驗版本。2019年JDK12-13,并行類的卸載AArch64架構支持。
? ? ? ? 2020年JDK14-15,windows & MacOS支持第一個正式版本。
? ? ? ? 2021年JDK16-17,亞毫秒級最大暫停時間并行線程數的自動計算。
? ? ? ? 2023年JDK21,支持分代年齡。
ZGC的使用方法
????????OracleJDK和OpenJDK中都支持ZGC,阿里的DragonWell龍井JDK也支持ZGC但屬于其自行對OpenJDK11的ZGC進行優化的版本。
????????建議使用JDK17之后的版本,延遲較低同時無需手動配置并行線程數。
分代ZGC添加參數啟用:-XX:+UseZGC -XX:+ZGenerational
非分代ZGC通過命令行選項啟用:-XX:+UseZGC
ZGC的參數設置
需要設置的參數:
-Xmx值最大堆內存大小
????????這是ZGC最重要的一個參數,必須設置。ZGC在運行過程中會使用一部分內存來處理垃圾回收,所以盡量保證堆中有足夠的空間。設置多少值取決于對象分配的速度,根據測試情況來決定。
可以設置的參數
-XX:SoftMaxHeapSize=值
????????ZGC會盡量保持堆內存小于該值,這樣在內存靠近這個值時會盡早地進行垃圾回收,但是依然有可能會超過該值。例如:-Xmx5g -XX:SoftMaxHeapSize=4g 這個參數設置,ZGC會盡量保證堆內存大小4GB,最多不會超過5GB。
3、JMH測試GC性能
pom
<properties><java.version>21</java.version>
</properties><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.37</version>
</dependency>
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.37</version>
</dependency>
測試代碼:
package com.lwz;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;//執行5輪預熱,每次持續2秒
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
//輸出毫秒單位
@OutputTimeUnit(TimeUnit.MILLISECONDS)
//統計方法執行的平均時間
@BenchmarkMode(Mode.AverageTime)
//java -jar xxx.jar -rf json
@State(Scope.Benchmark)
public class JavaGc {//每次測試對象大小4KB和4M@Param({"4", "4096"})int perSize;private void test(Blackhole blackhole) {//每次循環創建堆內存60%對象 JMX獲取到Java運行中的實時數據MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();//獲取到剩余的堆內存大小long heapSize = (long) ((heapMemoryUsage.getMax() - heapMemoryUsage.getUsed()) * 0.6);//計算循環的次數long size = heapSize / (1024L * perSize);for (int i = 0; i < 4; i++) {List<byte[]> list = new ArrayList<>((int) size);for (int j = 0; j < size; j++) {list.add(new byte[1024 * perSize]);}blackhole.consume(list);}}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseSerialGC"})public void serialGc(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseParallelGC"})public void parallelGc(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g"})public void g1(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseShenandoahGC"})public void shenandoahGc(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC"})public void zGc(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC","-XX:+ZGenerational"})public void zGcGenerational(Blackhole blackhole){test(blackhole);}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(JavaGc.class.getSimpleName()).forks(1).build();new Runner(opt).run();}
}
JVM Optimization Learning(五)
再小的努力,乘以365都很明顯!
一個程序員最重要的能力是:寫出高質量的代碼!!
有道無術,術尚可求也,有術無道,止于術。
無論你是年輕還是年長,所有程序員都需要記住:時刻努力學習新技術,否則就會被時代拋棄!