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底层相关的一些问题以及解决措施。