JVM调优 - JVM 面试题及答案
JVM调优
32.有哪些常用的命令行性能监控和故障处理工具?
操作系统工具
- top:显示系统整体资源使用情况
- vmstat:监控内存和CPU
- iostat:监控IO使用
- netstat:监控网络使用
JDK性能监控工具
- jps:虚拟机进程查看
- jstat:虚拟机运行时信息查看
- jinfo:虚拟机配置查看
- jmap:内存映像(导出)
- jhat:堆转储快照分析
- jstack:Java堆栈跟踪
- jcmd:实现上面除了jstat外所有命令的功能
33.了解哪些可视化的性能监控和故障处理工具?
以下是一些JDK自带的可视化性能监控和故障处理工具:
- JConsole

- VisualVM

- Java Mission Control

除此之外,还有一些第三方的工具:
- MAT
Java 堆内存分析工具。
- GChisto
GC 日志分析工具。
- GCViewer
GC 日志分析工具。
- JProfiler
商用的性能分析利器。
- arthas
阿里开源诊断工具。
- async-profiler
Java 应用性能分析工具,开源、火焰图、跨平台。
34.JVM的常见参数配置知道哪些?
一些常见的参数配置:
堆配置:
- -Xms:初始堆大小
- -Xms:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3表示年轻代和年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
- -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如3表示Eden: 3 Survivor:2,一个Survivor区占整个年轻代的1/5
- -XX:MaxPermSize=n:设置持久代大小
收集器设置:
- -XX:+UseSerialGC:设置串行收集器
- -XX:+UseParallelGC:设置并行收集器
- -XX:+UseParalledlOldGC:设置并行年老代收集器
- -XX:+UseConcMarkSweepGC:设置并发收集器
并行收集器设置
- -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数
- -XX:MaxGCPauseMillis=n:设置并行收集最大的暂停时间(如果到这个时间了,垃圾回收器依然没有回收完,也会停止回收)
- -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为:1/(1+n)
- -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况
- -XX:ParallelGCThreads=n:设置并发收集器年轻代手机方式为并行收集时,使用的CPU数。并行收集线程数
打印GC回收的过程日志信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
35.有做过JVM调优吗?
JVM调优是一件很严肃的事情,不是拍脑门就开始调优的,需要有严密的分析和监控机制,大概的一个JVM调优流程图:

实际上,JVM调优是不得已而为之,有那功夫,好好把烂代码重构一下不比瞎调JVM强。
但是,面试官非要问怎么办?可以从处理问题的角度来回答(对应图中事后),这是一个中规中矩的案例:电商公司的运营后台系统,偶发性的引发OOM异常,堆内存溢出。
1、因为是偶发性的,所以第一次简单的认为就是堆内存不足导致,单方面的加大了堆内存从4G调整到8G -Xms8g。
2、但是问题依然没有解决,只能从堆内存信息下手,通过开启了-XX:+HeapDumpOnOutOfMemoryError参数 获得堆内存的dump文件。
3、用JProfiler 对 堆dump文件进行分析,通过JProfiler查看到占用内存最大的对象是String对象,本来想跟踪着String对象找到其引用的地方,但dump文件太大,跟踪进去的时候总是卡死,而String对象占用比较多也比较正常,最开始也没有认定就是这里的问题,于是就从线程信息里面找突破点。
4、通过线程进行分析,先找到了几个正在运行的业务线程,然后逐一跟进业务线程看了下代码,有个方法引起了我的注意,导出订单信息。
5、因为订单信息导出这个方法可能会有几万的数据量,首先要从数据库里面查询出来订单信息,然后把订单信息生成excel,这个过程会产生大量的String对象。
6、为了验证自己的猜想,于是准备登录后台去测试下,结果在测试的过程中发现导出订单的按钮前端居然没有做点击后按钮置灰交互事件,后端也没有做防止重复提交,因为导出订单数据本来就非常慢,使用的人员可能发现点击后很久后页面都没反应,然后就一直点,结果就大量的请求进入到后台,堆内存产生了大量的订单对象和EXCEL对象,而且方法执行非常慢,导致这一段时间内这些对象都无法被回收,所以最终导致内存溢出。
7、知道了问题就容易解决了,最终没有调整任何JVM参数,只是做了两个处理:
- 在前端的导出订单按钮上加上了置灰状态,等后端响应之后按钮才可以进行点击
- 后端代码加分布式锁,做防重处理
这样双管齐下,保证导出的请求不会一直打到服务端,问题解决!
36.线上服务CPU占用过高怎么排查?
问题分析:CPU高一定是某个程序长期占用了CPU资源。

1、所以先需要找出那个进程占用CPU高。
- top 列出系统各个进程的资源占用情况。
2、然后根据找到对应进行里哪个线程占用CPU高。
- top -Hp 进程ID 列出对应进程里面的线程占用资源情况
3、找到对应线程ID后,再打印出对应线程的堆栈信息
- printf "%x\n" PID 把线程ID转换为16进制。
- jstack PID 打印出进程的所有线程信息,从打印出来的线程信息中找到上一步转换为16进制的线程ID对应的线程信息。
4、最后根据线程的堆栈信息定位到具体业务方法,从代码逻辑中找到问题所在。
查看是否有线程长时间的watting 或blocked,如果线程长期处于watting状态下, 关注watting on xxxxxx,说明线程在等待这把锁,然后根据锁的地址找到持有锁的线程。
37.内存飙高问题怎么排查?
分析: 内存飚高如果是发生在java进程上,一般是因为创建了大量对象所导致,持续飚高说明垃圾回收跟不上对象创建的速度,或者内存泄露导致对象无法回收。
1、先观察垃圾回收的情况
- jstat -gc PID 1000 查看GC次数,时间等信息,每隔一秒打印一次。
- jmap -histo PID | head -20 查看堆内存占用空间最大的前20个对象类型,可初步查看是哪个对象占用了内存。
如果每次GC次数频繁,而且每次回收的内存空间也正常,那说明是因为对象创建速度快导致内存一直占用很高;如果每次回收的内存非常少,那么很可能是因为内存泄露导致内存一直无法被回收。
2、导出堆内存文件快照
- jmap -dump:live,format=b,file=/home/myheapdump.hprof PID dump堆内存信息到文件。
3、使用visualVM对dump文件进行离线分析,找到占用内存高的对象,再找到创建该对象的业务代码位置,从代码和业务场景中定位具体问题。
38.频繁 minor gc 怎么办?
优化Minor GC频繁问题:通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过增大新生代空间-Xmn来降低Minor GC的频率。
39.频繁Full GC怎么办?
Full GC的排查思路大概如下:
- 清楚从程序角度,有哪些原因导致FGC?
- 大对象:系统一次性加载了过多数据到内存中(比如SQL查询未做分页),导致大对象进入了老年代。
- 内存泄漏:频繁创建了大量对象,但是无法被回收(比如IO对象使用完后未调用close方法释放资源),先引发FGC,最后导致OOM.
- 程序频繁生成一些长生命周期的对象,当这些对象的存活年龄超过分代年龄时便会进入老年代,最后引发FGC. (即本文中的案例)
- 程序BUG
- 代码中显式调用了gc方法,包括自己的代码甚至框架中的代码。
- JVM参数设置问题:包括总内存大小、新生代和老年代的大小、Eden区和S区的大小、元空间大小、垃圾回收算法等等。
- 清楚排查问题时能使用哪些工具
公司的监控系统:大部分公司都会有,可全方位监控JVM的各项指标。
JDK的自带工具,包括jmap、jstat等常用命令:
查看堆内存各区域的使用率以及GC情况
jstat -gcutil -h20 pid 1000
查看堆内存中的存活对象,并按空间排序
jmap -histo pid | head -n20
dump堆内存文件
jmap -dump:format=b,file=heap pid
可视化的堆内存分析工具:JVisualVM、MAT等
- 排查指南
- 查看监控,以了解出现问题的时间点以及当前FGC的频率(可对比正常情况看频率是否正常)
- 了解该时间点之前有没有程序上线、基础组件升级等情况。
- 了解JVM的参数设置,包括:堆空间各个区域的大小设置,新生代和老年代分别采用了哪些垃圾收集器,然后分析JVM参数设置是否合理。
- 再对步骤1中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用gc方法比较容易排查。
- 针对大对象或者长生命周期对象导致的FGC,可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析,需要先定位到可疑对象。
- 通过可疑对象定位到具体代码再次分析,这时候要结合GC原理和JVM参数设置,弄清楚可疑对象是否满足了进入到老年代的条件才能下结论。
40.有没有处理过内存泄漏问题?是如何定位的?
内存泄漏是内在病源,外在病症表现可能有:
- 应用程序长时间连续运行时性能严重下降
- CPU 使用率飙升,甚至到 100%
- 频繁 Full GC,各种报警,例如接口超时报警等
- 应用程序抛出
OutOfMemoryError错误 - 应用程序偶尔会耗尽连接对象
严重内存泄漏往往伴随频繁的 Full GC,所以分析排查内存泄漏问题首先还得从查看 Full GC 入手。主要有以下操作步骤:
使用
jps查看运行的 Java 进程 ID使用
top -p [pid]查看进程使用 CPU 和 MEM 的情况使用
top -Hp [pid]查看进程下的所有线程占 CPU 和 MEM 的情况将线程 ID 转换为 16 进制:
printf "%x\n" [pid],输出的值就是线程栈信息中的 nid。例如:
printf "%x\n" 29471,换行输出 731f。抓取线程栈:
jstack 29452 > 29452.txt,可以多抓几次做个对比。在线程栈信息中找到对应线程号的 16 进制值,如下是 731f 线程的信息。线程栈分析可使用 Visualvm 插件 TDA。
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fbe2c164000 nid=0x731f runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE使用
jstat -gcutil [pid] 5000 10每隔 5 秒输出 GC 信息,输出 10 次,查看 YGC 和 Full GC 次数。通常会出现 YGC 不增加或增加缓慢,而 Full GC 增加很快。或使用
jstat -gccause [pid] 5000,同样是输出 GC 摘要信息。或使用
jmap -heap [pid]查看堆的摘要信息,关注老年代内存使用是否达到阀值,若达到阀值就会执行 Full GC。如果发现
Full GC次数太多,就很大概率存在内存泄漏了使用
jmap -histo:live [pid]输出每个类的对象数量,内存大小(字节单位)及全限定类名。生成
dump文件,借助工具分析哪 个对象非常多,基本就能定位到问题在那了使用 jmap 生成 dump 文件:
# jmap -dump:live,format=b,file=29471.dump 29471 Dumping heap to /root/dump ... Heap dump file created- dump 文件分析
可以使用 jhat 命令分析:
jhat -port 8000 29471.dump,浏览器访问 jhat 服务,端口是 8000。通常使用图形化工具分析,如 JDK 自带的 jvisualvm,从菜单 > 文件 > 装入 dump 文件。
或使用第三方式具分析的,如 JProfiler 也是个图形化工具,GCViewer 工具。Eclipse 或以使用 MAT 工具查看。或使用在线分析平台 GCEasy。
**注意:**如果 dump 文件较大的话,分析会占比较大的内存。
- 在 dump 文析结果中查找存在大量的对象,再查对其的引用。
基本上就可以定位到代码层的逻辑了。
41.有没有处理过内存溢出问题?
内存泄漏和内存溢出二者关系非常密切,内存溢出可能会有很多原因导致,内存泄漏最可能的罪魁祸首之一。
排查过程和排查内存泄漏过程类似。
最近建一些几十个工作内推群,各大城市都有,群里目前已经收集了很多内推岗位,大厂、中厂、小厂、外包都有。 欢迎HR、开发、测试、运维和产品加入。

扫描下方微信,备注:网站+所在城市,即可拉你进工作内推群。
