面试官:如何进行 JVM 调优(附真实案例)

news2025/7/11 15:24:40

前言

面试官:在工作中做过 JVM 调优吗?讲讲做过哪些 JVM 调优?

我一个QPS不到10的项目,上次问我缓存穿透缓存雪崩,这次问我 JVM 调优,我是真滴难。

不过大家别慌,热心的我给大家找来了几个满分回答,大家选择合适的使用。

回答1:听好了,下面将是我第一次 JVM 调优。

回答2:我一般面试的时候才调优。

回答3:我一般直接加机器、加内存。

回答4:老子直接用的 ZGC,调个蛇皮。

正文

1、JVM 究竟需不需要调优?

JVM 经过这么多年的发展和验证,整体是非常健壮的。个人认为99%的情况下,基本用不到 JVM 调优。

通常来说,我们的 JVM 参数配置大多还是会遵循 JVM 官方的建议,例如:

  • -XX:NewRatio=2,年轻代:老年代=1:2

  • -XX:SurvivorRatio=8,eden:survivor=8:1

  • 堆内存设置为物理内存的3/4左右

  • 等等

JVM 参数的默认(推荐)值都是经过 JVM 团队的反复测试和前人的充分验证得出的比较合理的值,因此通常来说是比较靠谱和通用的,一般不会出大问题。

当然,更重要的是,大部分的应用 QPS 都不到10,数据量不到几万,这种低压环境下,想让 JVM 出问题,说实话也挺难的。

大部分同学更常遇到的应该是自己的代码 bug 导致 OOM、CPU load高、GC频繁啥的,这些场景也基本都是代码修复即可,通常不需要动 JVM。

当然,俗话说得好,凡事无绝对,还是有一小部分场景,是可能需要用到 JVM 调优的。具体哪些场景,我们在下面介绍。

值得一提的是,我们这边所说的 JVM 调优更多的是针对自己的业务场景对 JVM 参数进行优化调整,使其更适合我们的业务,而不是指对 JVM 源码的改动。

2、JVM 调优没有什么必要,使用性能更好的垃圾回收器就能解决问题了?

这是我在网上看到的一个说法,因为赞同的人比较多,我估计有不少同学也会有这个想法,因此在这边谈下自己的看法。

1)实战角度

不考虑应付面试的因素,升级垃圾回收器确实会是最有效的方式之一,例如:CMS 升级到 G1,甚至 ZGC。

这个很容易理解,更高版本的垃圾回收器相当于是 JVM 开发人员对 JVM 做的优化,人家毕竟是专门做这个的,所以通常来说升级高版本的性能会有不少的提升。

G1 目前已经有开始在逐渐应用开来,周围有不少团队在 JDK8 中使用了 G1,就我了解到的,还是存在不少问题的,不少同学在不断进行参数的调整,而在 JDK11 中能优化成啥样还有待验证。

ZGC 目前应用的还比较少,仅从对外公布的数据来看很好看,最大暂停时间不超过10ms,甚至是1ms,大家都抱有很高的期望。但是从目前我收集到的一些资料来看,ZGC 也并不是银弹,已知的明显问题有:

  • 吞吐量相较于 G1 会有所下降,官方称最大不超过15%

  • ZGC如果遇到非常高的对象分配速率(allocation rate)的话会跟不上,目前唯一有效的“调优”方式就是增大整个GC堆的大小来让ZGC有更大的喘息空间——R大与ZGC领队沟通后的原话

而且,随着后续 ZGC 应用开来,后续一定会不断出现更多问题的。

整体而言,个人觉得 JVM 调优在某些场景下还是有必要的,毕竟有句话叫:没有最好的,只有最合适的。

2)面试角度

如果你回答直接升级垃圾收集器,面试官可能也赞同,但是这个话题可能就这样结束了,面试官大概率没听到他想要的回答,你在这题的肯定拿不到加分,甚至可能会被扣分。

所以,在面试的时候,你可以回答升级垃圾收集器,但是你不能只回答升级垃圾收集器。

3、JVM 何时优化?

忌过早优化。《计算机程序设计艺术》的作者高德纳(Donald Ervin Knuth)曾说过一句经典的话:

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

真正的问题是,程序猿在错误的地方和错误的时间花了太多的时间担心效率问题;过早的优化是编程中所有(或者至少是大部分)罪恶的根源。

忌过早并不是说就完全不管,比较正确的做法应该是给核心服务的一些重要 JVM 指标配上监控告警,当指标出现波动或者异常时,能及时介入排查。

面试官:JVM 有哪些核心指标?合理范围应该是多少?

这个问题没有统一的答案,因为每个服务对AVG/TP999/TP9999等性能指标的要求是不同的,因此合理的范围也不同。

为了防止面试官追问,对于普通的 Java 后端应用来说,我这边给出一份相对合理的范围值。以下指标都是对于单台服务器来说:

  • 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以内尤佳

通常来说,只要这几个指标正常,其他的一般不会有问题,如果其他地方出了问题,一般都会影响到这几个指标。

4、JVM 优化步骤?

4.1、分析和定位当前系统的瓶颈

对于JVM的核心指标,我们的关注点和常用工具如下:

1)CPU指标

  • 查看占用CPU最多的进程

  • 查看占用CPU最多的线程

  • 查看线程堆栈快照信息

  • 分析代码执行热点

  • 查看哪个代码占用CPU执行时间最长

  • 查看每个方法占用CPU时间比例

常见的命令:

// 显示系统各个进程的资源使用情况
top
// 查看某个进程中的线程占用情况
top -Hp pid
// 查看当前 Java 进程的线程堆栈信息
jstack pid

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

2)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等。


3)JVM GC指标

  • 查看每分钟GC时间是否正常

  • 查看每分钟YGC次数是否正常

  • 查看FGC次数是否正常

  • 查看单次FGC时间是否正常

  • 查看单次GC各阶段详细耗时,找到耗时严重的阶段

  • 查看对象的动态晋升年龄是否正常

JVM 的 GC指标一般是从 GC 日志里面查看,默认的 GC 日志可能比较少,我们可以添加以下参数,来丰富我们的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

以上就是我们定位系统瓶颈的常用手段,大部分问题通过以上方式都能定位出问题原因,然后结合代码去找到问题根源。

4.2、确定优化目标

定位出系统瓶颈后,在优化前先制定好优化的目标是什么,例如:

  • 将FGC次数从每小时1次,降低到1天1次

  • 将每分钟的GC耗时从3s降低到500ms

  • 将每次FGC耗时从5s降低到1s以内

  • ...

4.3、制订优化方案

针对定位出的系统瓶颈制定相应的优化方案,常见的有:

  • 代码bug:升级修复bug。典型的有:死循环、使用无界队列。

  • 不合理的JVM参数配置:优化 JVM 参数配置。典型的有:年轻代内存配置过小、堆内存配置过小、元空间配置过小。

4.4、对比优化前后的指标,统计优化效果

4.5、持续观察和跟踪优化效果

4.6、如果还需要的话,重复以上步骤

5、调优案例:metaspace导致频繁FGC问题

以下案例来源于网络或本人真实经验,皆能自圆其说,理解掌握后同学们皆可拿来与面试官对线。

服务环境:ParNew + CMS + JDK8

问题现象:服务频繁出现FGC

原因分析

1)首先查看GC日志,发现出现FGC的原因是metaspace空间不够

对应GC日志:

Full GC (Metadata GC Threshold)

2)进一步查看日志发现元空间存在内存碎片化现象

对应GC日志:

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

这边简单解释下这几个参数的意义

  • used :已使用的空间大小

  • capacity:当前已经分配且未释放的空间容量大小

  • committed:当前已经分配的空间大小

  • reserved:预留的空间大小

这边 used 比较容易理解,reserved 在本例不重要可以先忽略,主要是 capacity 和 committed 这2个容易搞混。

结合下图来看更容易理解,元空间的分配以 chunk 为单位,当一个 ClassLoader 被垃圾回收时,所有属于它的空间(chunk)被释放,此时该 chunk 称为 Free Chunk,而 committed chunk 就是 capacity chunk 和 free chunk 之和。

之所以说内存存在碎片化现象就是根据 used 和 capacity 的数据得来的,上面说了元空间的分配以 chunk 为单位,即使一个 ClassLoader 只加载1个类,也会独占整个 chunk,所以当出现 used 和 capacity 两者之差较大的时候,说明此时存在内存碎片化的情况。

GC日志demo如下:

{Heap before GC invocations=0 (full 0):
 par new generation   total 314560K, used 141123K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
  eden space 279616K,  50% used [0x00000000c0000000, 0x00000000c89d0d00, 0x00000000d1110000)
  from space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
  to   space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
 concurrent mark-sweep generation total 699072K, used 0K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
  class space    used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
1.448: [Full GC (Metadata GC Threshold) 1.448: [CMS: 0K->10221K(699072K), 0.0487207 secs] 141123K->10221K(1013632K), [Metaspace: 35337K->35337K(1099776K)], 0.0488547 secs] [Times: user=0.09 sys=0.00, real=0.05 secs] 
Heap after GC invocations=1 (full 1):
 par new generation   total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
  eden space 279616K,   0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
  from space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
  to   space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
 concurrent mark-sweep generation total 699072K, used 10221K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
  class space    used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
}
{Heap before GC invocations=1 (full 1):
 par new generation   total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
  eden space 279616K,   0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
  from space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
  to   space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
 concurrent mark-sweep generation total 699072K, used 10221K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
  class space    used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
1.497: [Full GC (Last ditch collection) 1.497: [CMS: 10221K->3565K(699072K), 0.0139783 secs] 10221K->3565K(1013632K), [Metaspace: 35337K->35337K(1099776K)], 0.0193983 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
Heap after GC invocations=2 (full 2):
 par new generation   total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
  eden space 279616K,   0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
  from space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
  to   space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
 concurrent mark-sweep generation total 699072K, used 3565K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 17065K, capacity 22618K, committed 35840K, reserved 1079296K
  class space    used 1624K, capacity 2552K, committed 8172K, reserved 1048576K
}

元空间主要适用于存放类的相关信息,而存在内存碎片化说明很可能创建了较多的类加载器,同时使用率较低。

因此,当元空间出现内存碎片化时,我们会着重关注是不是创建了大量的类加载器。

3)通过 dump 堆存储文件发现存在大量 DelegatingClassLoader

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

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

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

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

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

优化策略:

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

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

总结

当被面试官问到 JVM 调优时,完全可以按照本文的脉络回答:

  • 首先表态如果使用合理的 JVM 参数配置,在大多数情况应该是不需要调优的——对应本文第1题

  • 其次说明可能还是存在少量场景需要调优,我们可以对一些 JVM 核心指标配置监控告警,当出现波动时人为介入分析评估——对应本文第3题

  • 最后举一个实际的调优例子来加以说明——对应本文第5题

如果面试官反问怎么分析排查的,则可以使用本文第4题的常用命令和工具来与之对线。

这一套流程下来,我相信大部分面试官都会对你印象不错。

最后

我是囧辉,一个坚持分享原创技术干货的程序员,如果觉得本文对你有帮助,记得点赞关注,我们下期再见。

推荐阅读

Java 基础高频面试题(2021年最新版)

Java 集合框架高频面试题(2021年最新版)

面试必问的 Spring,你懂了吗?

面试必问的 MySQL,你懂了吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/406191.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文学会IDEA中的Maven集成与创建(详细教程)

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

‘conda‘不是内部或外部命令,也不是可运行的程序或批处理文件。

Anaconda环境搭建常见问题 conda不是内部或外部命令,也不是可运行的程序或批处理文件。 解决方案:配置环境变量 1.找到Anaconda Nvaigator单机右键 2.更多 3.打开文件所在位置 4.继续Anaconda Nvaigator单机右键,更多,选择文件…

Xilinx XDMA驱动代码分析及用法

Xilinx XDMA驱动代码分析及用法 先简单的介绍一下,赛灵思的XDMA的驱动是用于做什么的、他的主要功能就类似与网卡pcie接口的网卡驱动、用于控制主机与fpga设备进行pcie的通讯。通讯的主要方式是设备文件的读写,这里不清楚的同学可以看一下我上一篇文章。…

IDEA连接MySQL数据库并执行SQL语句使用数据

文章目录一、IDEA连接MySQL数据库(一)首先新建普通Java项目(二)连接数据库1、点击右侧DataBase2、点击加号,找到MySQL,添加数据库3、输入用户名和密码,点击**Test Connection**4、显示连接成功&…

深度分析| 数据防泄露技术再次“翻红”的思考与建议

此文章由天空卫士供稿 01、数据防泄露的定义 数据防泄露(Data leakage prevention,DLP)是指通过一定的技术和管理,防止组织内敏感数据或信息资产,违反安全约束而丧失组织控制、破坏数据机密性的策略和措施。数据防泄…

【云原生】裸金属架构之服务器安装VMWare ESXI虚拟化平台详细流程

??作者简介:大家好,我是,运维领域创作者,??阿里云ACE认证高级工程师?? 个人主页: ??支持我:点赞??收藏留言?? ??格言:你未必出类拔萃,但一定与众不同!??…

[SSD4] 固态硬盘主控功能_SSD主控品牌

固态硬盘(Solid State Drives),简称SSD。它是一种电脑存储设备,由闪存(FLASH)、 闪存控制器、高速缓存(DRAM)组成。这是是固态硬盘的三个基本部件,对性能有关键影响。 了解一颗固态硬盘首先要从主控入手,主控对于固态硬盘的影响丝毫不亚于闪存。那么主控芯片对于固态硬盘…

来了!Python 官方发布了整套的中文PDF 文档(共27本)

1. 《Python中文指南》 学 Python 最好的学习资料永远是 Python 官方文档,可惜现在的官方文档大都是英文,虽然有中文的翻译版了,但是进度实在堪忧。 为了照顾英文不好的同学,我自己写了一份 面向零基础的朋友 的在线 Python 文档…

Stable diffusion安装踩坑(winMaciOS)

今天Apple官方支持了这个库,所以特意下载下来试试看,效果还不错,M芯片在深度学习领域第一次给人惊喜了。 https://machinelearning.apple.com/research/stable-diffusion-coreml-apple-silicon 1.win使用 安装 分享一下一个有意思的库的安…

算法的效率——时间复杂度和空间复杂度

文章目录1. 算法效率1.1 什么是算法1.2 算法的好坏2. 时间复杂度2.1 什么是时间复杂度2.2 时间复杂度的计算方法2.3 大O的渐进表示法2.4 常见时间复杂度计算举例3. 空间复杂度4. 常见复杂度对比1. 算法效率 1.1 什么是算法 目前普遍认可对算法的定义是:算法是解决…

通过CSS,H5,JavaScript实现表格表单的随机选择,和简单的随机点名。

目录 一、表格表单的随机选择 效果展示(表单内容可以根据自己需要进行修改) 1.H5的布局 第一步: 第二步: 2.CSS布局 3.JS的布局 第一步: 第二步: 第三步: 二、简单的随机点名 效果展…

软件测试简历项目经验怎么写,没有项目经验?

怎么办?只能接受低薪工作吗?No No No。本文就教大家如何解决这个问题。 为了帮助大家彻底解决“没有项目”这个拦路虎,接下来文章将从以下几个方面进行展开: 1、应该去哪找项目? 2、在众多的项目中,该怎么…

【AI作画】使用stable-diffusion-webui搭建AI作画平台

一、安装配置Anaconda 进入官网下载安装包https://www.anaconda.com/并安装,然后将Anaconda配置到环境变量中。 打开命令行,依次通过如下命令创建Python运行虚拟环境。 conda env create novelai python3.10.6E:\workspace\02_Python\novalai>conda…

基于FPGA 的TDC设计更好的选择——易灵思Quantum架构FPGA

时间数据转换器(TDC)常被用于测量时间间隔,被广泛应用于飞行时间测量领域。分辨率是一个重要的参数,但TDC线性度将直接影响整个系统的精度。尤其是近年来迅猛增长的汽车自动驾驶(ADAS)领域,利用光测距的激光雷达&#…

GCN-图卷积神经网络算法简单实现(含python代码)

本文是就实现GCN算法模型进行的代码介绍,上一篇文章是GCN算法的原理和模型介绍。 代码中用到的Cora数据集: 链接:https://pan.baidu.com/s/1SbqIOtysKqHKZ7C50DM_eA 提取码:pfny 文章目录 目的 一、数据集介绍 二、实现过程讲…

[闪存 2.1] 闪存芯片物理结构与_SLC/MLC/TLC/QLC

固态硬盘(Solid State Drives),简称SSD。它是一种电脑存储设备,由闪存(FLASH)、 闪存控制器、高速缓存(DRAM)组成。这是是固态硬盘的三个基本部件,对性能有关键影响。 闪存芯片简介闪存颗粒是固态

MySQL数据库的基础语法总结(1)

MySql一.数据库,数据表的基本操作1.数据库的基本操作2. 数据表的基本操作2.1 数据库的数据类型2.1.1 整数类型2.1.2 浮点数类型和定点数类型2.1.3 字符串类型2.1.4 日期与时间类型2.2 数据表的基本操作2.2.1 创建一个数据表2.2.2 查看数据表2.2.3 查看表的基本信息的MySQL指令2…

设计模式(上)

目录 1.设计模式概述 1.什么是设计模式 2.学习设计模式的意义 3.23种设计模式 4.七大设计原则 2.创建者模式 1.单例模式 2.工厂模式 3.抽象工厂模式 4.建造者模式 5.原型模式 3.结构型模式 1.适配器模式 2.桥接模式 3.代理模式 4.组合模式 5.装饰模式 6.享元…

[架构之路-24]:目标系统 - 系统软件 - C语言的结构与程序的工作原理 - 程序控制、函数调用栈、函数调用性能优化

目录 前言: 第1章 结构化程序与分层编程系统 1.1 计算机软硬件模型 1.2 程序的经典框架:算法数据结构 1.3 程序的结构化框架 1.4 程序的层次模型 1.5 程序设计的层次模型 第2章 C语言概述 2.1 C语言本质 2.2 C语言关键字 2.3 C语言在编程语言的…

js算法 字母大小写转换

题目:输入字符串将大写转换成小写,小写转换成大写? js字母大小写转换方法: 1、转换成大写:toUpperCase() 2、转换成小写:toLowerCase()方法一:把输入的字符串转成数组用split(&…