有没有进行过JVM调优,说说你的调优思路?
为什么要调优
- 防止出现OOM
- 解决OOM
- 减少Full GC出现的频率
前面铺垫了很多JVM的基础知识,这一讲我们终于来到了jvm调优课题。JVM调优是一个手段,但并不一定所有问题都可以通过JVM进行调优解决,因此,在进行JVM调优时,我们要遵循一些原则:
- 大多数的Java应用不需要进行JVM优化;
- 大多数导致GC问题的原因是代码层面的问题导致的(代码层面);
- 上线之前,应先考虑将机器的JVM参数设置到最优;
- 减少创建对象的数量(代码层面);
- 减少使用全局变量和大对象(代码层面);
- 优先架构调优和代码调优,JVM优化是不得已的手段(代码、架构层面);
- 分析GC情况优化代码比优化JVM参数更好(代码层面);
通过以上原则,我们发现,其实最有效的优化手段是架构和代码层面的优化,而JVM优化则是最后不得已的手段,也可以说是对服务器配置的最后一次“压榨”。接下来我们再看看如何进行JVM调优,调优不是盲目的,我们每修改一个JVM都有它背后的考究和数据支撑。在这里分享给大家一个比较通用的JVM调优的步骤 ⬇️
JVM调优的一般步骤为:
- 第1步:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
- 第2步:确定JVM调优量化目标;
- 第3步:确定JVM调优参数(根据历史JVM参数来调整);
- 第4步:调优一台服务器,对比观察调优前后的差异;
- 第5步:不断的分析和调整,直到找到合适的JVM参数配置;
- 第6步:找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点
前面提到每一个参数的修改都有它背后的数据做支撑。那数据是怎么来的呢?就是从我们GC
日志和dump
文件中分析出来的。这一讲我们就来仔细聊一聊我们通过什么工具去查看GC日志和dump文件,如何去分析GC日志和dump文件去找出症节所在。
GC日志分析
首先我们来看看GC日志分析,可能很多同学都没看到过GC日志,GC日志记录了垃圾收集器的运行情况,包括垃圾回收的原因、时间、执行的线程、回收的内存空间等信息。它主要用于分析垃圾收集器的性能和调优,在这里我们先来铺垫一下我们的工程是如何获取到GC日志的,然后我们在去讲工具讲分析。这里我们以Java程序、Tomcat项目、SpringBoot项目来讲解。
Java程序
Java程序可以通过在启动参数中添加相应的参数来开启GC日志的记录。具体而言,可以使用以下参数:
- -XX:+PrintGC:开启GC日志输出;
- -XX:+PrintGCDetails:在GC日志中输出详细的信息;
- -XX:+PrintGCDateStamps:在GC日志中输出时间戳;
- -Xlog:gc+heap=trace:在GC前后输出堆的详细信息;
- -Xlog:gc:<filename>:将GC日志输出到指定的文件中。
基于Java17版本:
例如,可以使用如下命令启动Java程序,并输出GC日志到文件gc.log中:
-Xms5M -Xmx5M -Xlog:gc:/Users/a123/IdeaProjects/java-example/logs/gc.log -XX:+PrintGC -Xlog:gc+heap=trace
在程序运行过程中,GC日志会被输出到指定的文件中,可以通过分析日志来了解垃圾收集的情况,进而进行优化和调优。
Tomcat
对于Tomcat项目,可以在Tomcat启动脚本中的JAVA_OPTS变量中添加相应的参数来开启GC日志的记录。具体而言,可以修改catalina.sh或catalina.bat(Windows下)文件,加入如下参数:
JAVA_OPTS="-Xloggc:/path/to/gc.log -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC"
其中,/path/to/gc.log为指定的日志输出文件路径。
对于Spring Boot项目,可以在application.properties或application.yml配置文件中添加以下参数:
logging.file=gc.log
logging.level.gc=info
其中,logging.file指定日志文件名,logging.level.gc指定日志输出级别,这里设置为info以输出GC日志。
需要注意的是,不同版本的Tomcat和Spring Boot可能会有所差异,具体添加参数的方式可能会有所不同。在实际使用中,应该查阅对应版本的文档或手册,了解如何正确地开启GC日志记录。
我们简单的来看一下GC日志:
[2023-02-14T22:13:12.554+0800] -XX:+PrintGC is deprecated. Will use -Xlog:gc instead.
[2023-02-14T22:13:12.557+0800] Using G1
[2023-02-14T22:13:12.593+0800] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 3M->3M(8M) 1.720ms
[2023-02-14T22:13:12.596+0800] GC(1) Pause Young (Concurrent Start) (G1 Preventive Collection) 4M->3M(8M) 1.533ms
[2023-02-14T22:13:12.596+0800] GC(2) Concurrent Mark Cycle
[2023-02-14T22:13:12.598+0800] GC(3) Pause Young (Normal) (G1 Preventive Collection) 4M->4M(8M) 1.069ms
[2023-02-14T22:13:12.601+0800] GC(4) To-space exhausted
[2023-02-14T22:13:12.601+0800] GC(4) Pause Young (Normal) (G1 Preventive Collection) 5M->5M(8M) 2.454ms
[2023-02-14T22:13:12.606+0800] GC(5) To-space exhausted
[2023-02-14T22:13:12.606+0800] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 6M->6M(8M) 4.494ms
[2023-02-14T22:13:12.617+0800] GC(6) Pause Full (G1 Compaction Pause) 6M->6M(8M) 10.797ms
[2023-02-14T22:13:12.627+0800] GC(7) Pause Full (G1 Compaction Pause) 6M->6M(8M) 10.258ms
[2023-02-14T22:13:12.627+0800] GC(2) Concurrent Mark Cycle 31.610ms
[2023-02-14T22:13:12.628+0800] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 6M->6M(8M) 0.243ms
[2023-02-14T22:13:12.636+0800] GC(9) Pause Full (G1 Compaction Pause) 6M->6M(8M) 8.079ms
[2023-02-14T22:13:12.646+0800] GC(10) Pause Full (G1 Compaction Pause) 6M->6M(8M) 10.570ms
[2023-02-14T22:13:12.647+0800] GC(11) Pause Young (Concurrent Start) (G1 Evacuation Pause) 6M->6M(8M) 0.307ms
[2023-02-14T22:13:12.647+0800] GC(13) Concurrent Mark Cycle
[2023-02-14T22:13:12.648+0800] GC(12) Pause Full (G1 Compaction Pause) 6M->1M(8M) 1.330ms
[2023-02-14T22:13:12.648+0800] GC(13) Concurrent Mark Cycle 1.388ms
日志文件直接看起来不是不是很直观看起来比较费劲。事实上分析GC日志可视化的工具很多很多,这里整理了一份:
- GCViewer:功能简单、易于使用,适合初学者。
- GCEasy:自动检测GC日志,并提供了一系列的报告和建议,适合快速定位和解决GC问题。
- HPROF:Java自带的堆内存分析工具,可以与GCViewer等工具结合使用,提供更全面的分析。
- Java Mission Control:JDK自带的性能监控工具,提供了包括GC在内的多种监控指标,并能与VisualVM等工具集成使用。
- VisualVM:功能全面、插件丰富,支持多种监控指标和分析方式,适合高级用户和专业人士。
- GClogAnalyzer:提供了多种图表和分析工具,能够深入分析GC日志的各个方面。
- YourKit Java Profiler:功能丰富、易于使用,提供了GC分析、CPU分析、内存分析等多种功能,适合专业人士使用。
- JProfiler:提供了多种性能分析和调试工具,包括GC分析、CPU分析、内存分析等,适合专业人士使用。
大家可以按照自己的喜好、习惯进行选择。同时JDK也我们提供了很多关于JVM调优的工具合集,在安装包的bin目录下面可以找到,我以我电脑举例:
/Library/Java/JavaVirtualMachines/jdk-17.0.5.jdk/Contents/Home/bin
上面是基于JDK17截图的Java工具集,已经删除了VisualVM了,在此我们基于GCEasy来进行GC日志分析。
官网地址为:https://gceasy.io/ 它有在线分析版和离线分析版都可以使用~
查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化。
举一个例子: 系统崩溃前的一些现象:
- 每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s
- FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
- 年老代的内存越来越大并且每次FullGC后年老代没有内存被释放
之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值,这个时候就需要分析JVM内存快照dump。
dump文件分析
讲完了我们的GC日志分析,我们再来看看我们的dump文件分析,dump文件记录了Java虚拟机在某个时间点的状态信息,包括Java虚拟机进程的堆栈信息、java对象的详细信息、线程信息、类信息等。它主要用于分析Java虚拟机的状态、诊断内存泄漏等问题。GC日志适合用于调优垃圾收集器,而dump文件适合用于分析Java虚拟机的状态信息。
老样子我们先来看看如何获取到我们的dump文件信息,先将工具再讲分析。
Java程序
- 使用命令行工具:如果您的Java应用程序在命令行上运行,则可以使用jmap和jstack命令行工具来生成dump文件。例如,要获取一个堆转储文件,您可以使用以下命令:
jmap -dump:format=b,file=<filename> <pid>
其中,<filename>是您想要为dump文件指定的名称,<pid>是您的Java应用程序的进程ID。
要获取一个线程转储文件,您可以使用以下命令:
jstack -F <pid> > <filename>
其中,<filename>是您想要为dump文件指定的名称,<pid>是您的Java应用程序的进程ID。
- 使用JMX API:如果您的Java应用程序正在运行,您可以使用Java Management Extensions(JMX)API获取dump文件。使用JMX API,您可以远程连接到应用程序并生成转储文件。有关如何使用JMX API获取dump文件的更多信息,请参阅Oracle的官方文档。
- 使用调试器:如果您在调试模式下运行您的Java应用程序,您可以使用调试器(如Eclipse、IntelliJ IDEA等)来获取dump文件。这些调试器通常提供了内存转储和线程转储的选项。
- 使用Java Flight Recorder(JFR):Java Flight Recorder是JDK自带的一个工具,用于记录应用程序在运行时的各种指标。您可以使用JFR来记录内存使用、线程、锁定信息等,并生成相应的dump文件。有关如何使用JFR的更多信息,请参阅Oracle的官方文档。
- 使用HeapDumpOnOutOfMemoryError参数:您可以通过在Java虚拟机参数中设置HeapDumpOnOutOfMemoryError参数来自动生成堆转储文件。当Java应用程序出现内存溢出错误时,JVM将自动生成一个转储文件,以便进行故障排除。例如:
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<filename> <your-class>
其中,<filename>是您想要为转储文件指定的路径和名称,<your-class>是您的Java应用程序的类名
- 使用第三方工具:还有许多第三方工具可用于生成Java程序的dump文件,例如VisualVM、MA(Memory Analyzer Tool)、JProfiler等。这些工具通常提供了丰富的功能,可以帮助您更好地分析和解决Java应用程序中的问题。
请注意,无论您使用哪种方法,生成dump文件都可能会对应用程序的性能产生一定的影响,因此请在必要时使用,并确保仅在测试或非生产环境中使用。
分析结果,判断是否需要优化
如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化,如果GC时间超过1-3秒,或者频繁GC,则必须优化。
如果满足下面的指标,则一般不需要进行GC:
- Minor GC执行时间不到50ms;
- Minor GC执行不频繁,约10秒一次;
- Full GC执行时间不到1s;
- Full GC执行频率不算频繁,不低于10分钟1次;
调整GC类型和内存分配
如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找1台或几台机器进行beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择。
不断的分析和调整
通过不断的试验和试错,分析并找到最合适的参数,如果找到了最合适的参数,则将这些参数应用到所有服务器。