【JVM】JVM调优原理、思路、真正意义上解决性能瓶颈(附实际调优案例)

在这里插入图片描述


更多相关内容可查看

JVM什么时候需要调优–面试的时候,所以本篇会针对面试的角度去描述JVM调优

JVM什么时候需要调优

  1. 性能下降
  • 响应时间变慢:应用程序的响应时间超过了可接受的阈值。
  • 处理速度变慢:任务处理的速度显著下降,特别是在高并发应用中。
  1. 垃圾回收问题
  • 频繁的 Full GC:如果 Full GC 的频率变得很高,可能会影响应用程序的性能。可以通过 JVM 的监控工具(如 Java VisualVM、JConsole)观察到这些情况。
  • 长时间停顿:垃圾回收引起的停顿时间过长,导致用户体验不佳。
  1. 内存使用情况
  • 内存泄漏:应用程序的内存使用量不断增加,但没有相应的释放,可能导致 OutOfMemoryError
  • 碎片化:老年代的内存碎片化,可能导致有效内存无法被使用。
  1. CPU 使用率高
  • 如果应用程序在运行过程中 CPU 使用率持续居高不下,尤其是在进行垃圾回收时,会需要调优。
  1. 线程问题
  • 线程争用:在多线程环境中,如果线程之间竞争过于激烈,导致性能瓶颈,通常需要调优线程的管理和调度。
  • 死锁:线程间的死锁情况可能导致程序无法继续执行,亦需进行调优。
  1. 应用程序扩展
  • 增加用户负载:当预计应用的用户负载增加时,可能需要重新评估并调优 JVM 设置,以适应变化。
  • 上线新功能或模块:添加新功能后,可能需要监控性能并进行相应的调优。
  1. 性能测试
  • 基准测试:在应用程序部署到生产环境前,通过负载测试发现性能瓶颈,通常会促使调优。
  1. 运行环境变化
  • 硬件升级:如果服务器硬件有所升级,可能需要重新评估和调优 JMV 参数以充分利用新硬件的性能。

本篇会以垃圾回收的角度去聊一个JVM调优的案例


调优思路(垃圾回收器方向)

直接升级垃圾回收器:比如将CMS切换到G1或者ZGC,但是你需要了解这三个垃圾回收器的原理,面试官会问你为什么切,切换能解决什么问题,有什么好处等等,可查看这篇一篇聊透内存泄露,栈内存溢出,JVM中的垃圾回收器超详细


调优思路(逐步排查)

分析问题、定位问题、解决问题,三个角度去描述,描述你具体怎么做的

分析问题

分析问题就必须知道一些具体的参数值是否在正常范围内,及一些分析问题的手段或者工具、比如:

CPU指标:

  • 查看占用CPU最多的进程
  • 查看占用CPU最多的线程
  • 查看线程堆栈快照信息
  • 分析代码执行热点
  • 查看哪个代码占用CPU执行时间最长
  • 查看每个方法占用CPU时间比例
    常见命令:
// 显示系统各个进程的资源使用情况
top
// 查看某个进程中的线程占用情况
top -Hp pid
// 查看当前 Java 进程的线程堆栈信息
jstack pid

常见的工具:JProfiler、JVM Profiler、Arthas等。


JVM 内存指标:

  • 查看当前 JVM 堆内存参数配置是否合理
  • 查看堆中对象的统计信息
  • 查看堆存储快照,分析内存的占用情况
  • 查看堆各区域的内存增长是否正常
  • 查看是哪个区域导致的GC
  • 查看GC后能否正常回收到内存

常见的命令:

// 查看当前的 JVM 参数配置
ps -ef | grep java
// 查看 Java 进程的配置信息,包括系统属性和JVM命令行标志
jinfo pid
// 输出 Java 进程当前的 gc 情况
jstat -gc pid
// 输出 Java 堆详细信息
jmap -heap pid
// 显示堆中对象的统计信息
jmap -histo:live pid
// 生成 Java 堆存储快照dump文件
jmap -F -dump:format=b,file=dumpFile.phrof pid

常见的工具:Eclipse MAT、JConsole等。


JVM GC指标

  • 查看每分钟GC时间是否正常
  • 查看每分钟YGC次数是否正常
  • 查看FGC次数是否正常
  • 查看单次FGC时间是否正常
  • 查看单次GC各阶段详细耗时,找到耗时严重的阶段
  • 查看对象的动态晋升年龄是否正常

GC日志常用 JVM 参数

// 打印GC的详细信息
-XX:+PrintGCDetails
// 打印GC的时间戳
-XX:+PrintGCDateStamps
// 在GC前后打印堆信息
-XX:+PrintHeapAtGC
// 打印Survivor区中各个年龄段的对象的分布信息
-XX:+PrintTenuringDistribution
// JVM启动时输出所有参数值,方便查看参数是否被覆盖
-XX:+PrintFlagsFinal
// 打印GC时应用程序的停止时间
-XX:+PrintGCApplicationStoppedTime
// 打印在GC期间处理引用对象的时间(仅在PrintGCDetails时启用)
-XX:+PrintReferenceGC

正常参数范围:

  • jvm.gc.time:每分钟的GC耗时在1s以内,500ms以内尤佳
  • jvm.gc.meantime:每次YGC耗时在100ms以内,50ms以内尤佳
  • jvm.fullgc.count:FGC最多几小时1次,1天不到1次尤佳
  • jvm.fullgc.time:每次FGC耗时在1s以内,500ms以内尤佳

知道了以上内容才能去评判是否出现了问题,以下案例参考网络

环境:ParNew + CMS + JDK8
问题:服务频繁出现FGC

所以对这个问题分析,我们就要看gc的日志进行分析

定位问题

查看gc日志:

java -Xms512m -Xmx1024m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar YourApp.jar
cat gc.log

在这里插入图片描述

根据日志可以分析问题:

Metaspace       used 35337K, capacity 56242K, committed 56320K, reserved 1099776K

参数含义:

  • used :已使用的空间大小
  • capacity:当前已经分配且未释放的空间容量大小
  • committed:当前已经分配的空间大小
  • reserved:预留的空间大小

结论:used 和 capacity 两者之差较大,说明此时存在内存碎片化的情况


继续分析为什么会出现内存碎片化的情况

生成dump 堆存储文件:

jmap -F -dump:format=b,file=dumpFile.phrof pid

然后把dump文件扔到Eclipse MAT中分析

Eclipse MAT使用方法:

在这里插入图片描述

分析如图(知道大概长什么样子,图示来源网络):

定位大对象:点击直方图图标(Histogram),对象会按内存大小排序,查看内存占用最大的对象

在这里插入图片描述

这个对象被谁引用:点击支配树(dominator tree),看大对象被哪个线程调用。这里可以看到是被主线程调用

在这里插入图片描述
定位具体代码:点击概述图标(thread_overview),看线程的方法调用链和堆栈信息,查看大对象所属类和第几行,定位到具体代码,解决问题。

在这里插入图片描述

分析结果:通过 dump 堆存储文件发现存在大量 DelegatingClassLoader

通过进一步分析,发现是由于反射导致创建大量 DelegatingClassLoader。其核心原理如下

在 JVM 上,最初是通过 JNI 调用来实现方法的反射调用,当 JVM 注意到通过反射经常访问某个方法时,它将生成字节码来执行相同的操作,称为膨胀(inflation)机制。如果使用字节码的方式,则会为该方法生成一个DelegatingClassLoader,如果存在大量方法经常反射调用,则会导致创建大量 DelegatingClassLoader。

反射调用频次达到多少才会从 JNI 转字节码?

默认是15次,可通过参数 -Dsun.reflect.inflationThreshold 进行控制,在小于该次数时会使用 JNI 的方式对方法进行调用,如果调用次数超过该次数就会使用字节码的方式生成方法调用。

分析结论:反射调用导致创建大量 DelegatingClassLoader,占用了较大的元空间内存,同时存在内存碎片化现象,导致元空间利用率不高,从而较快达到阈值,触发 FGC。

解决问题

1)适当调大 metaspace 的空间大小。

java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=1g -jar your-application.jar

2)优化不合理的反射调用。例如最常见的属性拷贝工具类 BeanUtils.copyProperties 可以使用 mapstruct 替换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来一杯龙舌兰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值