JVM线上调优实战

如果你的项目在生产环境内存资源占用异常,频繁FullGC,影响用户使用的流畅性及系统稳定性,绝大部分的问题都出自于业务代码本身的问题,在JVM调优里面也不例外,要减少GC的频率,进行JVM调优。

jvm参数

script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看当前程序启动时的jvm参数
java -XX:+PrintCommandLineFlags -version
# -XX:InitialHeapSize=523137920 -XX:MaxHeapSize=8370206720 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
# openjdk version "1.8.0_322"
# OpenJDK Runtime Environment (build 1.8.0_322-b06)
# OpenJDK 64-Bit Server VM (build 25.322-b06, mixed mode)

java -XX:+Print
# 查看jvm初始参数值
java -XX:+PrintFlagsInitial -version
# 查看jvm最终参数值
java -XX:+PrintFlagsFinal -version

# 查看这些参数一共有多少种
java -XX:+PrintFlagsInitial -version | wc -l
java -XX:+PrintFlagsFinal -version | wc -l
script
1
2
3
4
5
6
7
8
9
java -Xms500M -Xmx500M \  # 最小堆、最大堆 (2个设置相等即可)
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \ # 使用G1垃圾回收器
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause \ # 设置日志参数(5个20M文件,循环)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof \ # 线上环境蹦的时候自动导出堆dump文件 `dump.hprof`
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=50001 \
-jar \
-Ddefault.client.encoding="UTF-8" -Dfile.encoding="UTF-8" -Duser.language="Zh" -Duser.region="CN" \
app.jar \
--spring.profiles.active=prod

模拟实战

script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#   最小堆:30M 最大堆:30M  打印gc日志信息 
java -Xms30M -Xmx30M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01


# 查看当前所有java进程号pid
jps

# 实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器
top

# 显示指定进程信息
jinfo pid

# 显示堆内存信息
jstat -gc pid

# 动态显示 - 每1秒钟刷新一次
jstat -gc pid 1000

# 显示指定进程的所有线程 (一般用来查看是否存在死锁)
# ex: waiting on <0x0000000088ca3310> (a java.lang.Object)
# 假如有一个进程中100个线程,很多线程都在waiting on <xx> ,一定要找到是哪个线程持有这把锁
# 怎么找?搜索jstack dump的信息,找<xx> ,看哪个线程持有这把锁RUNNABLE
jstack pid # 一次性全部显示
jstack pid | more # 通过回车查看更多信息

# 查看指定进程前10行对象数据(内存分配的详细情况。例如实例个数,大小等)
jmap -histo pid | head -10

实战调优

你在项目中都使用了哪些参数打印GC?

具体的参数名称记不清楚了,但是我一般在项目中输出详细的 GC 日志,并加上可读性强的 GC 日志的时间戳。特别情况下我还会追加一些反映对象晋升情况和堆详细信息的日志,这些会单独打到gc.log文件中用来排查问题。另外,OOM 时自动 Dump 堆栈,我一般也会进行配置。

常用的调优工具有哪些?

JDK内置的命令行:jps(查看jvm进程信息)、jstat(监视jvm运行状态的,比如gc情况、jvm内存情况、类加载情况等)、jinfo(查看jvm参数的,也可动态调整)、jmap(生成dump文件的,在dump的时候会影响线上服务)、jhat(分析dump的,但是一般都将dump导出放到mat上分析)、jstack(查看线程的)。

JDK内置的可视化界面:JConsole、VisualVM,这两个在QA环境压测的时候很有用。

阿里巴巴开源的arthas:神器,线上调优很方便,安装和显示效果都很友好。

如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的?

检查下系统是否存在System.gc() ;

线上一个系统跑一段时间就栈溢出了,怎么办 ?

首先检查下是否有死归这种无限递归的程序或者递归方法太多

可以看下栈大小,若太小则可以指定-Xss参数设置栈大小

系统CPU经常100%,如何调优?

CPU100%,那肯定是有线程一直在占用着系统资源,所以具体方法如下:

  1. 找出哪个进程cpu占用高(top命令)
  2. 该进程中的哪个线程cpu占用高(top -Hp $pid命令)
  3. 将十进制的tid转化为十六进制(printf %x $tid命令)
  4. 导出该线程的堆栈 (jstack $pid >$pid.log命令)
  5. 查找哪个方法(栈帧)消耗时间 (less $pid.log)
  6. 可以确认工作线程占比高还是垃圾回收线程占比高
  7. 修改代码

系统内存飙高,如何查找问题?

  1. 找出哪个进程内存占用高(top命令)
  2. 查看jvm进程号(jps命令)
  3. 导出堆内存 (jmap命令生成dump文件,注意:线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿,所以操作前最好先从负载均衡里摘掉。)
  4. 分析dump文件 (比如mat软件)

大型项目如何进行性能瓶颈调优

  1. 数据库与SQL优化:一般dba负责数据库优化,比如集群主从等。研发负责SQL优化,比如索引、分库分表等。

  2. 集群优化:一般OP负责,让整个集群可以很容易的水平扩容,再比如tomcat/nginx的一些配置优化等。

  3. 硬件升级:选择最合适的硬件,充分利用资源。

  4. 代码优化:很多细节,可以参照阿里巴巴规范手册和安装sonar插件这种检测代码质量的工具。也可以适当的运用并行,比如CountDownLatch等工具。

  5. jvm优化:内存区域大小设置、对象年龄达到次数晋升老年代参数的调整、选择合适的垃圾收集器以及合适的垃圾收集器参数、打印详细的GC日志和oom的时候自动生成dump。

  6. 操作系统优化

你实际遇到调优的场景

之前都是采取zipkin做分布式链路追踪,后来换成了skywalking,所以将 zipkin 相关配置和代码都移除了,但是忘记移除maven坐标了,运行一段时间后导致了频繁full gc ,最后oom了。

因为配置了oom后自动生成dump文件,所以分析dump文件发现大对象是zipkin包里的ConcurrentHashMap$Node,通过观察zipkin的自动配置类ZipkinAutoConfiguration发现即使没有任何zipkin的配置,只要有zipkin的依赖都会创建一个异步报告者,默认的采样率是10%,所以即使不配置相关配置项,也会以默认采样率10%,发送到zipkin,这是默认的地址是http://localhost:9411/,此时发送到localhost:9411显然会连接拒绝。导致度量中的异常实例堆积,从而OOM。

1
2
3
4
5
6
private float probability = 0.1f;

@ConfigurationProperties("spring.zipkin")
public class ZipkinProperties {
private String baseUrl = "http://localhost:9411/";
}

附录

GC常用参数

  • -Xmn:年轻代
  • -Xms:最小堆
  • -Xmx:最大堆
  • -Xss:栈空间
  • -XX:+UseTLAB:使用TLAB,默认打开
  • -XX:+PrintTLAB:打印TLAB的使用情况
  • -XX:TLABSize:设置TLAB大小
  • -XX:+DisableExplictGC:禁用System.gc()不管用 ,防止FGC
  • -XX:+PrintGC:打印GC日志
  • -XX:+PrintGCDetails:打印GC详细日志信息
  • -XX:+PrintHeapAtGC:打印GC前后的详细堆栈信息
  • -XX:+PrintGCTimeStamps:打印时间戳
  • -XX:+PrintGCApplicationConcurrentTime:打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime:打印暂停时长
  • -XX:+PrintReferenceGC:记录回收了多少种不同引用类型的引用
  • -verbose:class:类加载详细过程
  • -XX:+PrintVMOptions:jvm参数
  • -XX:+PrintFlagsFinal:-XX:+PrintFlagsInitial 必须会用
  • -Xloggc:opt/log/gc.log:gc日志的路径以及文件名称
  • -XX:MaxTenuringThreshold:升代年龄,最大值15

Parallel常用参数

  • -XX:SurvivorRatio:年轻代中eden和from/to的比值。比如设置3就是eden:survivor=3:2,也就是from和to各占1,eden占用3
  • -XX:PreTenureSizeThreshold:大对象到底多大
  • -XX:MaxTenuringThreshold:升代年龄,最大值15
  • -XX:+ParallelGCThreads:并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy:自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC:设置年老代为并发收集
  • -XX:ParallelCMSThreads:CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction:使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection:在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction:多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled:年老代启用CMS,但默认是不会回收永久代(Perm)的。此处对Perm区启用类回收,防止Perm区内存满。
  • -XX:CMSInitiatingPermOccupancyFraction:达到什么比例时进行Perm回收
  • GCTimeRatio:设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis:停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

  • -XX:+UseG1GC:开启G1
  • -XX:MaxGCPauseMillis:建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis:GC的间隔时间
  • -XX:+G1HeapRegionSize:分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)
  • G1NewSizePercent:新生代最小比例,默认为5%
  • G1MaxNewSizePercent:新生代最大比例,默认为60%
  • GCTimeRatio:GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads:线程数量
  • InitiatingHeapOccupancyPercent:启动G1的堆空间占用比例

推荐文章

一次线上JVM调优实践的优化过程

JDK 内置实用工具:监视、故障排除

点击查看

本文标题:JVM线上调优实战

文章作者:LiJing

发布时间:2023年05月07日 - 10:07:21

最后更新:2023年06月03日 - 10:02:02

原始链接:https://blog-next.xiaojingge.com/posts/810947313.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------------本文结束 感谢您的阅读-------------------