1、前言

前面介绍了JVM相关的内存和线程相关的技术。对于JVM也算有了一个比较系统、完整的理论基础。理论总是作为指导实践的工具,但是从理论到实践,总会遇到一些虚拟机相关问题,故障。所以还需要学习一些常用的JVM排障工具,和一些常见的调优手段。

2、故障排查常用工具

2.1、命令行工具

2.1.1、jps

JVM Process Status Tool(jps)虚拟机进程状态工具。顾名思义主要用来查看虚拟机进程,并显示虚拟机执行主类名称和这些进程的状态等。同Linux的PS指令类似。

jps命令格式为:

jps [option] [hostid]

示例:

参数:

选项

作用

-q

只输出LVMID(进程的本地虚拟机唯一ID),省略主类名称

-m

输出虚拟机进程启动时传递给main()的参数

-l

输出主类的全限定名,如果进程执行的式jar包,则输出jar的路径

-v

输出虚拟机进程启动的JVM参数

2.1.2、jstat

JVM Statistics Monitoring Tool(jstat),虚拟机统计信息监控工具。用于监视虚拟机各种运行状态信息。可以显示虚拟机进程的类加载、内存、垃圾收集、即时编译等运行时数据。

jstat命令格式为:

jstat [option vmid [interval [s|ms] [count] ] ]

示例:

S0,S1:代表Survivor0、Survivor1区

E:表示Eden区

O:表示老年代

M:表示永久代(元空间)

CCS:表示压缩类空间

YGC:表示Young GC

YGCT:表示Young GC Time,耗时

FGC:表示Full GC

FGCT:表示Full GC Time,耗时

CGC:表示并发GC次数

CGCT:表示并发GC耗时

GCT:表示GCV Time,GC总耗时

/** * -gcutil 查看gc情况 * 7864 为进程pid * 500 每间隔500ms执行一次 * 10 总共执行10次 * 所以下面的命令为:每间隔500ms,查询一次进程id为7864的gc情况,一共查询10次 */jstat -gcutil 7864 500 10

参数(参数选项比较多,列举一些常用的,其他的自行查找):

选项

作用

-class

监视类加载信息、卸载数量、总空间以及类加载的耗时

-gc

监视Java堆情况

-gccause

功能同-gcutil,但是会额外输出导致上一次gc产生的原因

-gcutil

功能同-gc相同,但输出主要关注的已使用空间占总空间的百分比

-compiler

输出即使编译过的方法,耗时等

2.1.3、jmap

Memory Map for Java(jmap),用于生成JVM某一时刻运行的堆快照,即heapdump或dump文件。除了转储外,还可以查看当前的堆和方法区的详细信息等。

除了使用jmap,可以指定-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机出现内存溢出后自动生成dump文件。

注意:请尽可能不要在生产环境中使用jmap -dump来转储整个内存的dump文件。因为在dump过程中,会暂停所有执行线程的业务逻辑。会直接暂停线上业务的响应。

jmap命令格式为:

jmap [option ] vmid

示例:

参数:

选项

作用

-dump

生成Java堆快照。格式为:

jmap -dump:format=b,file=F://heap001.hprof {PID}

jmap -dump:live,format=b,file=F://heap001.hprof {PID} // live说明只dump出存活的对象

-heap

监视Java堆情况。只在Linux平台下有效

-histo

显示队中对象统计信息,包括类,实力数量,合计容量

-gcutil

功能同-gc相同,但输出主要关注的已使用空间占总空间的百分比

-F

当虚拟机进程堆-dump选项无响应时,可使用这个选项强制生成dump快照。只在Linux平台下有效

2.1.4、jhat

JVM Heap Analysis Tool(jhat),虚拟机堆快照分析工具。一般与jmap搭配使用。用来分析jmap生成的对快照。内置了一个微型的http/web服务器,分析结果可以通过浏览器访问既定的端口进行查看。但是个人建议可以使用第三方工具进行分析,如常用的MAT,jProfile等。

jhat命令格式为:

jhat -port 8080 d://xxx.hprof

接着访问http://localhost:8080即可。如果不指定-port 默认为7000。

示例:

2.1.5、jstack

Stack Trace for Java(jstack),堆栈跟踪工具。用于生成JVM当前时刻的线程快照,一般为threaddump或javacore文件。线程快照就是当前JVM内每一条线程正在执行的方法堆栈的集合,一般生成快照用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间挂起等原因。

jstack命令格式为:

jstack [option] vmid

示例:

参数:

选项

作用

-F

当正常输出的请求不被响应时,强制输出线程堆栈

-l

出堆栈外,显示关于锁的附加信息

-m

如果调用到本地方法的话,可以显示c/c++的堆栈

2.2、可视化工具

2.2.1、jConsole

Java Monitoring and Management Console(jConsole),Java监视与管理控制台。是一个基于JMX的可视化监控,管理工具。其中一项我常用的功能是通过JMX的MBean堆系统进行信息收集和参数动态调整。如一次线上的最大可用线程过载,就是通过MBean中临时调整参数恢复业务的。

2.2.2、jvisualvm

All-in-One Java Troubleshooting Tool(VisualVM),我认为是JDK自带的监控和故障处理能力最强大的程序之一。也是我们平时使用最经常的JVM排障工具。

它不仅包含了常规的运行监控,故障处理外,还提供了比如性能分析等额外功能。

另外jvisualvm还支持安装插件,可以通过插件平台扩展该工具的功能。

jvisualvm除了可以监视本地进程外,还可以通过JMX远程连接。java启动程序的时候,可以通过启动参数配置JMX相关信息即可。

在工具菜单栏选择插件,可以自由安装想要的插件:

2.3、第三方工具

2.3.1、jProfile

JProfiler是一个商业授权的Java剖析工具,由EJ技术有限公司,针对的Java EE和Java SE应用程序开发的。

它把CPU、执行绪和内存的剖析组合在一个强大的应用中。JProfiler可提供许多IDE整合和应用服务器整合用途。

JProfiler的是一个独立的应用程序,但其提供Eclipse和IntelliJ等IDE的插件。

它允许两个内存剖面评估内存使用情况和动态分配泄漏和CPU剖析,以评估线程冲突。

—-来自百度百科

2.3.2、MAT

Eclipse Memory Analyzer (MAT)是一个快速且功能丰富的Java堆分析器,可帮助您发现内存泄漏并减少内存消耗。

—-来自百度百科

2.3.3、arthas

arthas,阿里开源的Java实时性能监控和问题排查工具。强烈推荐。具体使用方式可以参照我另一篇博客《java线上项目排查,Arthas简单上手》

3、常用的调优策略

3.1、 编码阶段的预防

良好的编码习惯可以减少一些常见的问题,也能使程序的性能提高。以下例举常见的几个编码阶段问题:

3.1.1、避免短命大对象

如byte[]。JVM中,大对象需要大量连续的内存空间,如很长的字符串或者元素数量很庞大的数组。jvm在分配空间时,有时候需要提前进行GC,以获取足够的空间分配。可以通过-XX:PretenureSizeThreshold指定大于该值的对象直接分配在老年代。

3.1.2、变量作用域

尽可能控制变量的作用域范围,尽可能不要定义全局变量。

3.1.3、String操作

String操作,尽可能使用Stringbuffer或者Stringbuilder,尽量减少字符串的+拼接。

3.1.4、锁操作

注意锁的粒度和范围。尽可能精确缩小锁的粒度。能不用锁就不用锁,能锁区块就不要锁整个方法体,能用对象锁,就不要用类锁。

3.2、部署阶段的预防

3.2.1、选用合适的硬件和软件设施

很多性能问题,大部分可以通过氪金的方式解决。如果通过氪金的方式解决,效率是最高的(当然要看实际情况)。这里指的选用合适版本并不是氪金,选用配置越高越好,主要指的是做好程序的规划,业务体量,以及良好的程序设计和部署用例。做好CPU密集型和IO密集型的业务处理,如果IO密集型的,就选用IOPS高的磁盘等等。

3.2.2、压力测试

程序编码完成后,做一些压力测试。一个好的程序不仅要满足功能需求,更需要满足非功能性需求。压力测试正式验证这些非功能性需求是否负责指标的一个有力手段。宁愿问题在测试阶段发生,也尽量避免在生产阶段发生。

3.3、常用的JVM参数配置

-Xms512M 设置Heap 空间最小值

-Xmx512M 设置Heap 空间最大值

-Xmn200M 设置Young区大小

-Xss 256K 设置线程栈大小

-XX:MaxGCPauseMillis=500 垃圾回收器最大的停顿时间

-XX:+UseG1GC 指定G1垃圾回收器(具体使用哪个垃圾回收期可以视程序要求而定)

-XX:NewRatio 新老生代的比值

-XX:+HeapDumpOnOutOfMemoryError 启动堆内存溢出打印

-XX:HeapDumpPath=heap.hprof 指定Heap快照打印位置

-XX:MinHeapFreeRatio jvm heap 在使用率小于n时,heap进行收缩

-XX:MaxHeapFreeRatio jvm heap 在使用率大于n时,heap进行扩张

3.4、问题排查和分析

3.4.1、CPU过载

CPU过高,可以通过top命令排查各个进程的资源占用情况。结合-Hp参数找到CPU过多的线程。

如果一台服务器部署应用过多,出现CPU争用情况,可以自主分配具体CPU核心,错开CPU核心使用。具体参照我另一篇博客《windows系统启动java程序限制cpu核心数》

3.4.2、线程死锁

可以使用jstack或者jvisualvm或arthas分析当前锁的情况进行排查。

3.4.3、OOM

出现OOM,先要观察报错类型。

  • java.lang.OutOfMemoryError: java heap space

  • heap溢出.检查是否存在内存泄漏问题. 分析堆栈对象情况

  • java.lang.OutOfMemoryError: Meta space

  • 可通过JVM类加载的情况进行排查.jstat或者实时监测工具排查..

  • java.lang.OutOfMemoryError:GC over head limit exceeded

  • 系统处于高频的GC状态,而且回收的效果依然不佳的情况分析GC日志曲线集合堆栈分析排查.

关于OOM问题,个人经验在启动参数中加入-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof 指定溢出时候的dump文件生成目录,一旦生产出现OOM问题,需要紧急恢复情况下,大多数会采用重启的方案(前提是单机应用,且业务需要紧急恢复的情况,而且重启能解决99%的问题,如果不行,那就重启2次)。一旦重启后,内存镜像丢失就不利于接下来的问题分析和定位。所以配置完参数后,出现oom问题,系统自动帮我们dump当下的内存文件,我们就可以放心大胆重启了。然后再逐步分析hprof文件。

……

应该还有很多,这就不一一例举了,目前仅能想到这么多。

4、小结

前面几篇分别介绍了JVM的一些结构体系。对于Java程序员来说,不管是项目中,还是面试,经常会遇到关于JVM相关的问题。同时通过对JVM的深入学习,能够更加从容应对JVM底层相关的一些问题以及解决措施。