JVM调优思路

JVM调优思路

原文链接:
https://blog.csdn.net/sinat_40770656/article/details/122203566

监控命令与调优工具

首先,我们需要了解有哪些常用的性能监控和jvm调优的工具:

  • 操作系统:
    • top: 显示系统整体资源使用情况
    • vmstat:监控系统内存和CPU
    • iostat:监控系统IO使用情况
    • netstat:监控系统网络使用情况

  • JDK性能监控工具
    • jps:虚拟机进程查看
    • jstat:虚拟机运行时信息查看
    • jinfo:虚拟机配置查看
    • jmap:内存映像
    • jhat:堆转储快照分析
    • jstack:java堆栈跟踪
    • jcmd:实现以上除了jstack所有功能

  • jdk可视化性能监控工具:
    • JConsole
    • VisualVM
    • Java Mission Control

  • 三方工具:
    • MAT:java堆内存分析工具
    • GChisto:GC日志分析工具
    • GCViewer:GC日志分析工具
    • JProfiler:商用的性能分析器
    • arthas:阿里开源诊断工具(https://arthas.aliyun.com/doc/)
    • async-profiler:java应用性能分析工具,开源,火焰图、跨平台

JVM常见的参数配置

传送门:jvm参数详解

比较常见的一些参数配置:

  • 堆配置

    • -Xms:初始堆大小
    • -Xmx:最大堆大小
    • -XX:NewSize :设置年轻代大小
    • -XX:PermSize:设置老年代大小
    • ······
  • GC收集器配置

    • -XX:+UseParNewGC :设置年轻代为并行收集
    • -XX:+UseParallelOldGC :年老代垃圾收集方式为并行收集(Parallel Compacting)
    • -XX:+UseConcMarkSweepGC:使用CMS内存收集
    • ······
  • ······

有做过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参数,只是做了两个处理:

  • 在前端的导出订单按钮上加上了置灰状态,等后端响应之后按钮才可以进行点击
  • 后端代码加分布式锁,做防重处理

这样双管齐下,保证导出的请求不会一直打到服务端,问题解决!


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

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

1、 所以,先找出那个进程占用CPU高。

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

2、然后,根据找到对应进程里那个线程占用CPU高。

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

3、找到对应进程后,再打印出对应线程的堆栈信息。

  • printf “%x\n” PID 把线程ID转换为16进制
  • jstack PID 打印出进程的所有线程信息,从打印出来的线程信息中找到上一步转换为16进制的线程ID对应的线程信息

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

查看是否有线程长时间的waitting或blocked,如果线程长期出入waitting状态下,关注waitting on xxxxx,说明线程在等待这把锁,然后根据锁的地址找到持有锁的线程。


内存飙高问题怎么排查?

分析: 内存飙高如果是发生在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文件进行离线解析,找到占用内存高的对象,再找到创建该对象的业务代码位置,从代码和业务场景中定位具体具体问题。


频繁 minor gc 怎么办?

通常情况下,可能是年轻代空间较小,Eden区很快被填满,导致频繁的minor gc;因此,可以调大年轻代空间(-Xmx),来降低minor gc次数。


频繁full gc 怎么办?

思路:

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

  • 大对象:系统一次性加载了过多数据到内存中(比如SQL查询未做分页),导致大对象进入了老年代。
  • 内存泄漏:频繁创建了大量对象,但是无法被回收(比如IO对象使用完后未调用close方法释放资源),先引发FGC,最后导致OOM。
  • 程序BUG
  • 代码中显示调用了System.gc(),包括自己的代码和依赖jar包中的代码。
  • JVM参数设置问题:包括总内存大小、新生代和老年代大小、Eden区、S0区、S1区、元空间大小、垃圾回收算法等等。

2、清楚排查问题时使用哪些工具

  • 公司的监控系统: 大部分公司都会用,可全方位监控JVM的各项指标。
  • JDK自带的工具,包括jmap、jstat等常用命令
# 查看堆内存各区域的使用率以及GC情况
jstat -gcutil -h20 pid 1000
# 查看堆内存中存活对象,并按空间排序
jmap -histo pid | head -n20
# dump堆内存文件
jmap -dump:format=b,file=heap pid
  • 可视化的堆内存分析工具:VisualVM、MAT等

3、排查指南

  • 查看监控,以了解出现问题的时间点以及当前FGC的频率(对比正常频率)
  • 了解该时间点之前有没有程序上线、基础组件升级等情况。
  • 了解JVM的参数设置,包括:堆空间各个区域的大小设置,新生代和老年代分别采用了哪些垃圾收集器,然后分析JVM参数设置是否合理。
  • 再对步骤1中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用gc方法比较容易排查。
  • 针对大对象或者长生命周期对象导致的FGC,可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析,需要先定位到可疑对象。
  • 通过可疑对象定位到具体代码再次分析,这时候要结合GC原理和JVM参数设置,弄清楚可疑对象是否满足了进入到老年代的条件才能下结论。

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

内存泄漏是内在病源,外在症状可能为:

  • 应用程序长时间连续运行时,可能性能严重下降。
  • CPU使用率飙升,甚至到100%。
  • 频繁Full GC,各种报警,例如接口超时报警等。
  • 应用程序抛出OutOfMemoryError错误。
  • 应用程序偶尔会耗尽连接对象。

严重的内存泄漏往往伴随着频繁的Full GC,所以分析排查内存泄漏问题首先还得从查看 Full GC 入手。主要有以下操作步骤:

  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。

  5. 抓取线程栈:jstack 29452 > 29452.txt,可以多抓几次做个对比。
    在线程栈信息中找到对应线程号的 16 进制值,如下是 731f 线程的信息。线程栈分析可使用 Visualvm 插件 TDA。

  6. 使用 jstat -gcutil [pid] 5000 10 每隔 5 秒输出 GC 信息,输出 10 次,查看 YGC 和 Full GC 次数。通常会出现 YGC 不增加或增加缓慢,而 Full GC 增加很快。
    或使用 ** jstat -gccause [pid] 5000 ** ,同样是输出 GC 摘要信息。
    或使用 jmap -heap [pid] 查看堆的摘要信息,关注老年代内存使用是否达到阀值,若达到阀值就会执行 Full GC。

  7. 如果发现 Full GC 次数太多,就很大概率存在内存泄漏了

  8. 使用 **jmap -histo:live [pid] ** 输出每个类的对象数量,内存大小(字节单位)及全限定类名。

  9. 生成 dump 文件,借助工具分析哪 个对象非常多,基本就能定位到问题在那了
    使用 jmap 生成 dump 文件:

    # jmap -dump:live,format=b,file=29471.dump 29471 
    
  10. dump 文件分析:
    可以使用 jhat 命令分析:jhat -port 8000 29471.dump,浏览器访问 jhat 服务,端口是 8000。
    通常使用图形化工具分析,如 JDK 自带的 jvisualvm,从菜单 > 文件 > 装入 dump 文件。
    或使用第三方式具分析的,如 JProfiler 也是个图形化工具,GCViewer 工具。Eclipse 或以使用 MAT 工具查看。或使用在线分析平台 GCEasy。

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

  11. 在 dump 文析结果中查找存在大量的对象,再查对其的引用。基本上就可以定位到代码层的逻辑了。


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

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

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

你可能感兴趣的:(基础知识,java,jvm)