Skip to content

《面渣逆袭》JVM 篇 · 第 3/4 章。原版 PDF(下载 / 打印)

32.有哪些常用的命令行性能监控和故障处理工具?

操作系统工具

top :显示系统整体资源使用情况

vmstat :监控内存和 CPU

iostat :监控 IO 使用

netstat :监控网络使用

JDK 性能监控工具

jps :虚拟机进程查看

jstat :虚拟机运行时信息查看

jinfo :虚拟机配置查看

jmap :内存映像(导出)

jhat :堆转储快照分析

jstack :Java 堆栈跟踪

jcmd :实现上面除了 jstat 外所有命令的功能

33.了解哪些可视化的性能监控和故障处理工具?

以下是一些 JDK 自带的可视化性能监控和故障处理工具:

JConsole

配图

VisualVM

配图

JavaMissionControl

配图

除此之外,还有一些第三方的工具:

M AT

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 : 3Survivor :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 。

  1. 但是问题依然没有解决,只能从堆内存信息下手,通过开启了-

XX:+HeapDumpOnOutOfMemoryError 参数 获得堆内存的 dump 文件。

  1. 用 JProfiler 对 堆 dump 文件进行分析,通过 JProfiler 查看到占用内存最大的对象是 String 对

象,本来想跟踪着 String 对象找到其引用的地方,但 dump 文件太大,跟踪进去的时候总是卡死,而

String 对象占用比较多也比较正常,最开始也没有认定就是这里的问题,于是就从线程信息里面找突破点。

  1. 通过线程进行分析,先找到了几个正在运行的业务线程,然后逐一跟进业务线程看了下代码,有个

方法引起了我的注意,导出订单信息

  1. 因为订单信息导出这个方法可能会有几万的数据量,首先要从数据库里面查询出来订单信息,然后

把订单信息生成 excel ,这个过程会产生大量的 String 对象。

  1. 为了验证自己的猜想,于是准备登录后台去测试下,结果在测试的过程中发现导出订单的按钮前端

居然没有做点击后按钮置灰交互事件,后端也没有做防止重复提交,因为导出订单数据本来就非常慢,使用的人员可能发现点击后很久后页面都没反应,然后就一直点,结果就大量的请求进入到后台,堆内存产生了大量的订单对象和 EXCEL 对象,而且方法执行非常慢,导致这一段时间内这些对象都无法被回收,所以最终导致内存溢出。

  1. 知道了问题就容易解决了,最终没有调整任何 JVM 参数,只是做了两个处理:

在前端的导出订单按钮上加上了置灰状态,等后端响应之后按钮才可以进行点击后端代码加分布式锁,做防重处理这样双管齐下,保证导出的请求不会一直打到服务端,问题解决!

36.线上服务 CPU 占用过高怎么排查?

问题分析:CPU 高一定是某个程序长期占用了 CPU 资源。

配图

  1. 所以先需要找出那个进程占用 CPU 高。

top 列出系统各个进程的资源占用情况。

  1. 然后根据找到对应进行里哪个线程占用 CPU 高。

top-Hp 进程 ID 列出对应进程里面的线程占用资源情况

  1. 找到对应线程 ID 后,再打印出对应线程的堆栈信息

printf"%x\n"PID 把线程 ID 转换为 16 进制。

jstackPID 打印出进程的所有线程信息,从打印出来的线程信息中找到上一步转换为 16 进制的线程 ID 对应的线程信息。

  1. 最后根据线程的堆栈信息定位到具体业务方法, 从代码逻辑中找到问题所在。

查看是否有线程长时间的 watting 或 blocked ,如果线程长期处于 watting 状态下, 关注 watting

onxxxxxx ,说明线程在等待这把锁,然后根据锁的地址找到持有锁的线程。

37.内存飙高问题怎么排查?

分析: 内存飚高如果是发生在 java 进程上,一般是因为创建了大量对象所导致,持续飚高说明垃圾回收跟不上对象创建的速度,或者内存泄露导致对象无法回收。

  1. 先观察垃圾回收的情况

jstat-gcPID1000 查看 GC 次数,时间等信息,每隔一秒打印一次。

jmap-histoPID|head-20 查看堆内存占用空间最大的前 20 个对象类型, 可初步查看是哪个对象占用了内存。

如果每次 GC 次数频繁,而且每次回收的内存空间也正常,那说明是因为对象创建速度快导致内存一直占用很高;如果每次回收的内存非常少,那么很可能是因为内存泄露导致内存一直无法被回收。

  1. 导出堆内存文件快照

jmap-dump:live,format=b,file=/home/myheapdump.hprofPIDdump 堆内存信息到文件。

  1. 使用 visualVM 对 dump 文件进行离线分析,找到占用内存高的对象,再找到创建该对象的业务

代码位置,从代码和业务场景中定位具体问题。

38.频繁 minor gc 怎么办?

优化 MinorGC 频繁问题:通常情况下,由于新生代空间较小,Eden 区很快被填满,就会导致频繁

MinorGC ,因此可以通过增大新生代空间**-Xmn**来降低 MinorGC 的频率。

39.频繁 Full GC 怎么办?

FullGC 的排查思路大概如下:

  1. 清楚从程序角度,有哪些原因导致 FGC ?

大对象:系统一次性加载了过多数据到内存中(比如 SQL 查询未做分页),导致大对象进入了老年代。

内存泄漏:频繁创建了大量对象,但是无法被回收(比如 IO 对象使用完后未调用 close 方法释放资源),先引发 FGC ,最后导致 OOM.程序频繁生成一些长生命周期的对象,当这些对象的存活年龄超过分代年龄时便会进入老年代,最后引发 FGC. (即本文中的案例)

程序 BUG

代码中显式调用了 gc方法,包括自己的代码甚至框架中的代码。

JVM 参数设置问题:包括总内存大小、新生代和老年代的大小、Eden 区和 S 区的大小、元空间大小、垃圾回收算法等等。

  1. 清楚排查问题时能使用哪些工具

公司的监控系统:大部分公司都会有,可全方位监控 JVM 的各项指标。

JDK 的自带工具,包括 jmap 、jstat 等常用命令:

查看堆内存各区域的使用率以及GC情况

jstat-gcutil-h20pid1000 # 查看堆内存中的存活对象,并按空间排序

jmap-histopid|head-n20 #dump 堆内存文件

jmap-dump:format=b,file=heappid可视化的堆内存分析工具:JVisualVM 、MAT 等

  1. 排查指南

查看监控,以了解出现问题的时间点以及当前 FGC 的频率(可对比正常情况看频率是否正常)

了解该时间点之前有没有程序上线、基础组件升级等情况。

了解 JVM 的参数设置,包括:堆空间各个区域的大小设置,新生代和老年代分别采用了哪些垃圾收集器,然后分析 JVM 参数设置是否合理。

再对步骤 1 中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用 gc 方法比较容易排查。

针对大对象或者长生命周期对象导致的 FGC ,可通过 jmap-histo 命令并结合 dump 堆内存文件作进一步分析,需要先定位到可疑对象。

通过可疑对象定位到具体代码再次分析,这时候要结合 GC 原理和 JVM 参数设置,弄清楚可疑对象是否满足了进入到老年代的条件才能下结论。

40.有没有处理过内存泄漏问题?是如何定位的?

内存泄漏是内在病源,外在病症表现可能有:

应用程序长时间连续运行时性能严重下降

CPU 使用率飙升,甚至到 100%频繁 FullGC ,各种报警,例如交叉超时报警等应用程序抛出OutOfMemoryError错误应用程序偶尔会耗尽连接对象严重内存泄漏往往伴随频繁的FullGC,所以分析排查内存泄漏问题首先还得从查看 FullGC 入手。

主要有以下操作步骤:

  1. 使用jps查看运行的 Java 进程 ID

  2. 使用**top-p[pid]**查看进程使用 CPU 和 MEM 的情况

  3. 使用**top-Hp[pid]**查看进程下的所有线程占 CPU 和 MEM 的情况

  4. 将线程 ID 转换为 16 进制:printf"%x\n"[pid],输出的值就是线程栈信息中的nid

例如:printf"%x\n"29471,换行输出731f

  1. 抓取线程栈:jstack29452>29452.txt,可以多抓几次做个对比。

在线程栈信息中找到对应线程号的 16 进制值,如下是731f线程的信息。线程栈分析可使用

Visualvm 插件TDA

"ServiceThread"#7daemonprio=9os_prio=0tid=0x00007fbe2c164000nid=0x731frunnable

[0x0000000000000000]

java.lang.Thread.State:RUNNABLE

  1. 使用jstat-gcutil[pid]500010每隔 5 秒输出 GC 信息,输出 10 次,查看YGCFullGC

次数。通常会出现 YGC 不增加或增加缓慢,而 FullGC 增加很快。

或使用jstat-gccause[pid]5000,同样是输出 GC 摘要信息。

或使用**jmap-heap[pid]**查看堆的摘要信息,关注老年代内存使用是否达到阀值,若达到阀值就会执行 FullGC 。

  1. 如果发现FullGC次数太多,就很大概率存在内存泄漏了
8)使用jmap -histo:live [pid] 输出每个类的对象数量,内存大小(字节单位)及全限定类名。
  1. 生成dump文件,借助工具分析哪 个对象非常多,基本就能定位到问题在那了

使用 jmap 生成 dump 文件:

#jmap-dump:live,format=b,file=29471.dump29471

Dumpingheapto/root/dump...

Heapdumpfilecreated

  1. dump 文件分析

可以使用jhat命令分析:jhat-port800029471.dump,浏览器访问 jhat 服务,端又是 8000 。

通常使用图形化工具分析,如 JDK 自带的jvisualvm,从菜单 > 文件 > 装入 dump 文件。

或使用第三方式具分析的,如JProfiler也是个图形化工具,GCViewer工具。Eclipse 或以使用

MAT 工具查看。或使用在线分析平台GCEasy

注意:如果 dump 文件较大的话,分析会占比较大的内存。

  1. 在 dump 文析结果中查找存在大量的对象,再查对其的引用。

基本上就可以定位到代码层的逻辑了。

41.有没有处理过内存溢出问题?

内存泄漏和内存溢出二者关系非常密切,内存溢出可能会有很多原因导致,内存泄漏最可能的罪魁祸首之一。

排查过程和排查内存泄漏过程类似。

本文整理自三分恶《面渣逆袭》系列的公开内容,仅供个人学习使用

本站仅供个人学习使用,请勿外传