我们对JVM进行调优的时候,首先需要了解调优的基本概念,什么是调优,以及调优的目的
在调整性能时,JVM有三个组件:
- 堆大小调整
- 垃圾收集器调整
- JIT编译调整
大多数调优选项都与调整堆大小和看项目情况选择最合适的垃圾收集器有关。JIT编译器堆性能也有很大影响,但是很少需要使用较新版本的JVM进行调优。
通常,在调优Java应用程序时,重点是以下两个主要目标之一:
响应性:应用程序或系统对请求的数据进行响应的速度,对于专注于响应性的应用程序,长的暂停时间是不可以接受的,重点是在短时间内做出回应。
吞吐量:侧重于在特定时间内最大化应用程序的工作量,对于专注于吞吐量的应用程序,高暂停时间是可接受的。由于高吞吐量应用程序在较长时间内专注于基准测试,因此不需要考虑快速响应时间。
系统瓶颈核心还是在应用代码,一般情况下无需过多调优,而且JVM也在不断的优化。
下面是JVM常用参数
-XX:-AlwaysPreTouch | jvm启动时分配内存,非使用时再分配。 |
-XX:ErrorFile=filename | 崩溃日志 |
-XX:TraceClassLoading | 跟踪类加载信息 |
-XX:+PrintClassHistogram | 按下Ctrl+Break后,打印类的信息 |
-Xmx -Xms | 最大堆和最小堆 |
-xx:perSize、-xx:metaspaceSize | 永久代/元数据空间 |
-XX:+HeapDumpOnOutOfMemoryError | OOM时导出堆到文件 |
-XX:+HeaoDumpPath | OOM时堆导出的路径 |
-XX:OnOutOfMemoryError | 在OOM时,执行一个脚本 |
java -XX:+PrintFlagsFinal -version | 打印所有的-XX参数和默认值 |
GC调优思路
- 分析场景,例如:启动速度慢;偶尔出现相应鳗鱼平均水平或者出现卡顿
- 确定目标,内存占用、低延时、吞吐量
- 收集日志,通过参数配置收集GC日志;通过JDK工具查看GC状态
- 分析日志,使用工具辅助分析日志,查看GC次数,GC时间
- 调整参数,切换垃圾收集器或者调整垃圾收集器参数
通用GC参数
-XX:ParallelGCThreads | 并行GC数量 |
-XX:ConcGCThreads | 并发GC数量 |
-XX:MaxGCPauseMillis | 最大停顿时间,单位毫秒,GC尽力保证回收时间不超过设定值 |
-XX:GCTimeRatio | 0-100的取值范围;垃圾收集时间占总时间的比,默认99,即最大允许1%时间做GC |
-XX:SurvivorRatio | 设置Eden区大小和Survivor区大小的比例,8表示两个Survivor:Eden=2:8,即一个SUrvivor占年轻代的1/10 |
-XX:NewRatio | 新生代和老年代的比,4表示新生代:老年代=1:4,即年轻代占堆的1/5 |
-verbose:gc、-XX:printGC | 打印GC的简要信息 |
-XX:+PrintGCDetails | 打印GC详细信息 |
-XX:+PrintGCTimeStamps | 打印GC发生的时间戳 |
-Xloggc:log/gc.log | 指定GC log的位置,以文件输出 |
-XX:+PrintHeapAtGC | 每一次GC,后都打印堆信息 |
下面进行调优测试:
java版本:java version "12.0.1" 2019-04-16
Java(TM) SE Runtime Environment (build 12.0.1+12)
Java HotSpot(TM) 64-Bit Server VM (build 12.0.1+12, mixed mode, sharing)
//启动线程,模拟用户请求
//每100毫秒创建150线程,每个线程创建一个512kb的对象,最多一秒存在1500线程,
@SpringBootApplication
public class GcTestApplication {
public static void main(String[] args) {
SpringApplication.run(GcTestApplication.class, args);
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
new Thread(() -> {
for (int i = 0; i< 150; i++){
try {
byte[] temp = new byte[1024*512];
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
},100,100, TimeUnit.MILLISECONDS);
}
}
进行打包 mvn、clean、package
在服务器上执行:java -Xmx1024m -Xlog:gc:gc.log -jar gc-test-0.0.1-SNAPSHOT.jar
对象存活在1秒左右的场景,远远超过平时接口的响应时间的要求,应该为吞吐量优先的场景。
GC分析,主要查看GC导致的stop-the-world,这导致我们的程序延时的增大。
查找到程序的进程号
jcmd | grep "gc-test-0.0.1-SNAPSHOT.jar" | awk '{print $1}'
jmap打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况
jmap -heap ${jcmd | grep "gc-test-0.0.1-SNAPSHOT.jar" | awk '{print $1}'}
执行程序
java -Xmx1024m -Xlog:gc:gc.log -jar gc-test-0.0.1-SNAPSHOT.jar
分析GC日志
GCViewer工具,辅助分析GC文件:https://github.com/chewiebug/GCViewer
也可以通过jstat -gc -h10 pid 1000动态监控GC统计信息,1000毫秒统计一次,每十行输出一次标题
打开GCViewer后将log文件拖进去即可
垃圾收集器Parallel参数调优:1.JDK默认的收集器,2.吞吐量优先
-XX:+UseParallelGC | 新生代使用并行回收收集器 |
-XX:+UseParallelOldGC | 老年代使用并行回收收集器 |
-XX:ParallelGCThreads | 设置由于垃圾回收的线程数 |
-XX:_UseAdaptiveSizePolicy | 打开自适应GC策略 |
垃圾收集器CMS参数调优
- 响应时间优先1
- Parallel GC无法满足应用程序延迟要求时再考虑使用CMS垃圾收集器
- 新版建议使用G1垃圾收集器
-XX:+UseConcMarkSweepGC | 新生代使用并行收集器,老年代使用CMS+串行收集器 |
-XX:+UseParNewGC | 在新生代中使用并行收集器,CMS下默认开启 |
-XX:CMSInitiatingOccupancyFraction | 设置触发GC的阈值默认68%,如果不幸内存预留空间不够,就会引起concurrent mode failure |
-XX:+UseCMSCompactAtFullCollection | Full GC后进行一次整理,整理过程是独占的,会引起停顿时间变长 |
-XX:+ClassFullGCsBeforeCompaction | 设置进行几次full GC后,进行一次碎片整理 |
-XX:+CMSClassUnloadingEnable | 允许对类元数据进行回收 |
-XX:+UseCMSInitiatingOccupancyOnly | 表示只在到达阈值的时候,才进行CMS回收 |
XX:CMSIncrementalMode | 使用增量模式,比较适合单CPU |
垃圾收集器G1参数调优
- 兼顾吞吐量和响应时间
- 超过50%的java堆被实时数据占用
- 建议大堆(大于6G)
- 且GC延迟要求有限的应用(稳定且可预测的暂停时间低于0.5秒)
-XX:G1HeapRegionSize=<N,例如16<N | 设置region大小,默认heap/200 |
-XX:G1MixedGCLiveThresholdPercent | 老年代依靠Mixed GC,触发阈值 |
-XX:G1OldCSetRegionThresholdPercent | 定多被包含在一次Mixed GC中的region比例 |
-XX:+ClassUnloadingWithConcurrentMark | G1增加冰魔人开启,在并发标记阶段结束后,JVM就进行类型卸载 |
-XX:G1NewSizePercent | 新生代的最小比例 |
-XX:G1MixNewSizePercent | 新生代的最大比例 |
-XX:G1MixedGCCountTarget | Mixed GC的数量控制 |
运行时JIT编译器优化参数
JIT编译指的是字节码编译为本地代码(汇编)执行,只有热点代码才会编译为本地代码。解释器执行节约内存,反之可以使用编译执行来提升效率