【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战

1. 监控工具

1. jvisualvm(JDK内置)

2. jconsole(JDK内置)

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第1张图片

3. jmc(JDK内置)

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第2张图片

4. Jprofile(第三方)

5. Eclipse Memory Analyzer

6. JvisualVM插件

2. JAVA命令行工具

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第3张图片

2.1 jps虚拟机进程状况工具

常用的几个参数:

-l   输出java应用程序的main class的完整包

-q 仅显示pid,不显示其它任何相关信息

-m 输出传递给main方法的参数

-v 输出传递给JVM的参数。在诊断JVM相关问题的时候,这个参数可以查看JVM相关参数的设置

2.2 jstat虚拟机统计信息监视工具

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]

vmid是虚拟机ID,在Linux/Unix系统上一般就是进程IDinterval是采样时间间隔。count是采样数目。比如下面输出的是GC信息,采样时间间隔为250ms,采样数为4

1

2

3

4

5

6

root@ubuntu:/# jstat -gc 21711 250 4

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU       YGC     YGCT    FGC    FGCT     GCT   

192.0  192.0   64.0   0.0    6144.0   1854.9   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649

192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649

192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649

192.0  192.0   64.0   0.0    6144.0   2109.7   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649

要明白上面各列的意义,先看JVM堆内存布局:

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第4张图片

1

2

堆内存 = 年轻代 + 年老代 + 永久代

年轻代 = Eden区 + 两个Survivor区(From和To)

  现在来解释各列含义:

1

2

3

4

5

6

7

S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)

EC、EU:Eden区容量和使用量

OC、OU:年老代容量和使用量

PC、PU:永久代容量和使用量

YGC、YGCT:年轻代GC次数和GC耗时

FGC、FGCT:Full GC次数和Full GC耗时

GCT:GC总耗时

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第5张图片

2.3 jinfo配置信息工具

观察运行中的java程序的运行环境参数:参数包括Java System属性和JVM命令行参数
实例:jinfo 2083
其中2083就是java进程id号,可以用jps得到这个id号。
输出内容太多了,不在这里一一列举,大家可以自己尝试这个命令。

2.4 jmap内存映像工具

 jmapMemory Map)和  jhatJava Heap Analysis Tool

 jmap用来查看堆内存使用状况,一般结合jhat使用。

  jmap语法格式如下:

1

2

3

jmap [option] pid

jmap [option] executable core

jmap [option] [server-id@]remote-hostname-or-ip

1

jmap -permstat pid

打印进程的类加载器和类加载器加载的持久代对象信息,输出:类加载器名称、对象是否存活(不可靠)、对象地址、父类加载器、已加载的类大小等信息

使用jmap -heap pid查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况。

使用jmap -histo[:live] pid  查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象

 还有一个很常用的情况是:用jmap把进程内存使用情况dump到文件中,再用jhat分析查看。jmap进行dump命令格式如下:

1

jmap -dump:format=b,file=dumpFileName

1

2

3

root@ubuntu:/# jmap -dump:format=b,file=/tmp/dump.dat 21711     

Dumping heap to /tmp/dump.dat ...

Heap dump file created

  dump出来的文件可以用MATVisualVM等工具查看,这里用jhat查看

1

2

3

4

5

6

7

8

9

10

root@ubuntu:/# jhat -port 9998 /tmp/dump.dat

Reading from /tmp/dump.dat...

Dump file created Tue Jan 28 17:46:14 CST 2014

Snapshot read, resolving...

Resolving 132207 objects...

Chasing references, expect 26 dots..........................

Eliminating duplicate references..........................

Snapshot resolved.

Started HTTP server on port 9998

Server is ready.

 然后就可以在浏览器中输入主机地址:9998查看

2.5 jstack命令(Java Stack Trace)

jstack主要用来查看某个Java进程内的线程堆栈信息。语法格式如下:

jstack [option] pid

jstack [option] executable core

jstack [option] [server-id@]remote-hostname-or-ip

命令行参数选项说明如下:

-l   long listings,会打印出额外的锁信息,在发生死锁时可以用  

      jstack -l pid来观察锁持有情况

-m   mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native    

      方法)

3. 监控与分析

3.1 堆信息查看

3.1.1 用途

有了堆信息查看方面的功能,我们一般可以顺利解决以下问题:

  --年老代年轻代大小划分是否合理

  --内存泄漏

  --垃圾回收算法设置是否合理

3.1.2 可查看内容

可查看堆空间大小分配(年轻代、年老代、持久代分配)

提供即时的垃圾回收功能

垃圾监控(长时间监控回收情况)

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第6张图片

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第7张图片

查看堆内类、对象信息查看:数量、类型等

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第8张图片

对象引用情况查看

3.2 线程监控

3.2.1 用途

Dump线程详细信息:查看线程内部运行情况

死锁检查

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第9张图片

线程信息监控:系统线程数量。

线程状态监控:各个线程都处在什么样的状态下

3.3 热点分析(抽样器)

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第10张图片

    CPU热点:检查系统哪些方法占用的大量CPU时间

    内存热点:检查哪些对象在系统中数量最大(一定时间内存活对象和销毁对象一起统计)

    这两个东西对于系统优化很有帮助。我们可以根据找到的热点,有针对性的进行系统的瓶颈查找和进行系统优化,而不是漫无目的的进行所有代码的优化。

3.3.1 查看方法CPU耗时

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第11张图片

3.3.2 查看线程CPU耗时

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第12张图片

3.3.3 查看线程内存分配情况

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第13张图片

3.3.4 查看对象占用内存情况

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第14张图片

3.3.5 查看持久代内存占用情况

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第15张图片

3.4 快照

       快照是系统运行到某一时刻的一个定格。在我们进行调优的时候,不可能用眼睛去跟踪所有系统变化,依赖快照功能,我们就可以进行系统两个不同运行时刻,对象(或类、线程等)的不同,以便快速找到问题。

       举例说,我要检查系统进行垃圾回收以后,是否还有该收回的对象被遗漏下来的了。那么,我可以在进行垃圾回收前后,分别进行一次堆情况的快照,然后对比两次快照的对象情况。

3.5 缓冲区查看

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第16张图片

3.5.1 可视化垃圾回收

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第17张图片

4. JAVA基础命令详解

4.1 javac

用法:javac <选项> <源文件>

其中,可能的选项包括:

  -g                         生成所有调试信息

  -g:none                    不生成任何调试信息

  -g:{lines,vars,source}     只生成某些调试信息

  -nowarn                    不生成任何警告

  -verbose                   输出有关编译器正在执行的操作的消息

  -deprecation               输出使用已过时的 API 的源位置

  -classpath <路径>            指定查找用户类文件的位置

  -cp <路径>                   指定查找用户类文件的位置

  -sourcepath <路径>           指定查找输入源文件的位置

  -bootclasspath <路径>        覆盖引导类文件的位置

  -extdirs <目录>              覆盖安装的扩展目录的位置

  -endorseddirs <目录>         覆盖签名的标准路径的位置

  -d <目录>                    指定存放生成的类文件的位置

  -encoding <编码>             指定源文件使用的字符编码

  -source <版本>               提供与指定版本的源兼容性

  -target <版本>               生成特定 VM 版本的类文件

  -version                   版本信息

  -help                      输出标准选项的提要

  -X                         输出非标准选项的提要

  -J<标志>                     直接将 <标志> 传递给运行时系统

4.2 jar

用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件] [-C 目录] 文件名 ...

选项:

    -c  创建新的存档

    -t  列出存档内容的列表

    -x  展开存档中的命名的(或所有的〕文件

    -u  更新已存在的存档

    -v  生成详细输出到标准输出上

    -f  指定存档文件名

    -m  包含来自标明文件的标明信息

    -0  只存储方式;未用ZIP压缩格式

    -M  不产生所有项的清单(manifest〕文件

    -i  为指定的jar文件产生索引信息

    -C  改变到指定的目录,并且包含下列文件:

如果一个文件名是一个目录,它将被递归处理。

清单(manifest〕文件名和存档文件名都需要被指定,按'm' 和 'f'标志指定的相同顺序。

 

示例1:将两个class文件存档到一个名为 'classes.jar' 的存档文件中:

       jar cvf classes.jar Foo.class Bar.class

示例2:用一个存在的清单(manifest)文件 'mymanifest' 将 foo/ 目录下的所有

           文件存档到一个名为 'classes.jar' 的存档文件中:

       jar cvfm classes.jar mymanifest -C foo/ .

4.3 javadoc

javadoc: 错误 - 未指定软件包或类。

用法:javadoc [选项] [软件包名称] [源文件] [@file]

-overview <文件>          读取 HTML 文件的概述文档

-public                   仅显示公共类和成员

-protected                显示受保护/公共类和成员(默认)

-package                  显示软件包/受保护/公共类和成员

-private                  显示所有类和成员

-help                     显示命令行选项并退出

-doclet <类>              通过替代 doclet 生成输出

-docletpath <路径>        指定查找 doclet 类文件的位置

-sourcepath <路径列表>    指定查找源文件的位置

-classpath <路径列表>     指定查找用户类文件的位置

-exclude <软件包列表>     指定要排除的软件包的列表

-subpackages <子软件包列表> 指定要递归装入的子软件包

-breakiterator            使用 BreakIterator 计算第 1 句

-bootclasspath <路径列表> 覆盖引导类加载器所装入的

                          类文件的位置

-source <版本>            提供与指定版本的源兼容性

-extdirs <目录列表>       覆盖安装的扩展目录的位置

-verbose                  输出有关 Javadoc 正在执行的操作的消息

-locale <名称>            要使用的语言环境,例如 en_US 或 en_US_WIN

-encoding <名称>          源文件编码名称

-quiet                    不显示状态消息

-J<标志>                  直接将 <标志> 传递给运行时系统

 

通过标准 doclet 提供:

-d <目录>                         输出文件的目标目录

-use                              创建类和软件包用法页面

-version                          包含 @version 段

-author                           包含 @author 段

-docfilessubdirs                  递归复制文档文件子目录

-splitindex                       将索引分为每个字母对应一个文件

-windowtitle <文本>               文档的浏览器窗口标题

-doctitle              包含概述页面的标题

-header                包含每个页面的页眉文本

-footer                包含每个页面的页脚文本

-bottom                包含每个页面的底部文本

-link                        创建指向位于 的 javadoc 输出的链接

-linkoffline          利用位于 的软件包列表链接至位于

的文档

-excludedocfilessubdir <名称 1>:..排除带有给定名称的所有文档文件子目录。

-group <名称> :..         在概述页面中,将指定的软件包分组

-nocomment                        抑止描述和标记,只生成声明。

-nodeprecated                     不包含 @deprecated 信息

-noqualifier <名称 1>:<名称 2>:...从输出中排除限定符的列表。

-nosince                          不包含 @since 信息

-notimestamp                      不包含隐藏时间戳

-nodeprecatedlist                 不生成已过时的列表

-notree                           不生成类分层结构

-noindex                          不生成索引

-nohelp                           不生成帮助链接

-nonavbar                         不生成导航栏

-serialwarn                       生成有关 @serial 标记的警告

-tag <名称>:<位置>:<标题>         指定单个变量自定义标记

-taglet                           要注册的 Taglet 的全限定名称

-tagletpath                       Taglet 的路径

-charset <字符集>                 用于跨平台查看生成的文档的字符集。

-helpfile <文件>                  包含帮助链接所链接到的文件

-linksource                       以 HTML 格式生成源

-sourcetab <制表符长度>           指定源中每个制表符占据的空格数

-keywords                         使软件包、类和成员信息附带 HTML 元标记

-stylesheetfile <路径>            用于更改生成文档的样式的文件

-docencoding <名称>               输出编码名称

4.4 rmid

rmid: 非法选项:-?

用法:rmid

 

其中,

  -port

  -log     指定 rmid 将日志写入的目录

  -stop               停止当前的 rmid 调用(对指定端口)

  -C    向每个子进程传递参数(激活组)

  -J    向 java 解释程序传递参数

5. 常见问题分类

5.1 内存泄露

详见 JVM原理及优化之十: JVM内存泄漏专题

5.2 GC性能消耗高

  1. GC操作时间过长
  2. GC全量操作

5.3 JVM CPU 使用率高

以下是两个可能的原因:

  1. 复杂正则导致 CPU 使用率高
  2. HashMap 在并发访问下导致 CPU 使用率高

     HashMap 是非线程安全的,在并发访问的情况下就可能出现死循环,这个死循环的分析网上很多了。Spring 的缓存模块(spring-modules-cache-0.7.jar)用它作为缓存,在平时并发访问度不高,没有问题,被恶意扫描时,就触发了死循环

6. 问题定位

给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。这里说的数据包括:运行日志、异常堆栈、GC日志、线程快照(threaddump/javacore文件)、堆转储快照(heapdump/hprof文件)等。

7. 故障处理

7.1 案例1(线程死锁)

使用jconsole工具可以检测线程死锁,如下图:

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战_第18张图片

7.2 案例2(异常日志问题)

问题描述:

生产环境抛异常,但却没有将堆栈信息输出到日志,可以确定的是日志输出时用的是log.error("xx发生错误", e)
问题分析:

它跟JDK5的一个新特性有关,对于一些频繁抛出的异常,JDK为了性能会做一个优化,JIT重新编译后会抛出没有堆栈的异常。
而在使用-server模式时,该优化选项是开启的,因此在频繁抛出某个异常一段时间后,该优化开始起作用,即只抛出没有堆栈的异常信息。
问题解决:

由于该优化是在JIT重新编译后才起作用,因此起初抛出的异常还是有堆栈的,所以可以查看较旧的日志,寻找完整的堆栈信息。
另一个解决办法是暂时禁用该优化,即强制要求每次都要抛出有堆栈的异常,幸好JDK提供了通过配置JVM参数的方式来关闭该优化。
-XX:-OmitStackTraceInFastThrow,便可禁用该优化了(注意选项中的减号,加号则表示启用)

7.3 案例3(高CPU占用)

问题描述:

生产环境下的某台tomcat7服务器,在刚发布时的时候一切都很正常,在运行一段时间后就出现CPU占用很高的问题,基本上是负载一天比一天高。

问题分析:

1. 程序属于CPU密集型,和开发沟通过,排除此类情况。

2. 程序代码有问题,出现死循环,可能性极大。

问题解决:

1. 开发那边无法排查代码某个模块有问题,从日志上也无法分析得出。

2. 记得原来通过strace跟踪的方法解决了一台PHP服务器CPU占用高的问题,但是通过这种方法无效,经过google搜索,发现可以通过下面的方法进行解决,那就尝试下吧。

解决过程:

1. 根据top命令,发现PID为2633的Java进程占用CPU高达300%,出现故障。

2. 找到该进程后,如何定位具体线程或代码呢,首先显示线程列表,并按照CPU占用高的线程排序:

[root@localhost logs]# ps -mp 2633 -o THREAD,tid,time | sort -rn

 

显示结果如下:

USER     %CPU PRI SCNT WCHAN  USER SYSTEM   TID     TIME

root     10.5  19    - -         -      -  3626 00:12:48

root     10.1  19    - -         -      -  3593 00:12:16

 

找到了耗时最高的线程3626,占用CPU时间有12分钟了!

 

将需要的线程ID转换为16进制格式:

[root@localhost logs]# printf "%x\n" 3626

e18

 

最后打印线程的堆栈信息:

[root@localhost logs]# jstack 2633 |grep e18 -A 30

将输出的信息发给开发部进行确认,这样就能找出有问题的代码。

通过几天的监控,CPU已经安静下来了。

 

该专题是一个系列,参照了一系列JVM资料,对JVM基础知识做了摘要总结,并结合实战做了总结:

【基础+实战】JVM原理及优化系列之一:JVM体系结构

【基础+实战】JVM原理及优化系列之二:JVM内存管理

【基础+实战】JVM原理及优化系列之三:JVM垃圾收集器

【基础+实战】JVM原理及优化系列之四:JVM参数说明

【基础+实战】JVM原理及优化系列之五:JVM默认设置

【基础+实战】JVM原理及优化系列之六:JVM主要调优参数

【基础+实战】JVM原理及优化系列之七:JVM调优注意事项

【基础+实战】JVM原理及优化系列之八:如何查看JVM参数配置?

【基础+实战】JVM原理及优化系列之九:JVM监控、分析与故障处理实战

【基础+实战】JVM原理及优化系列之十:JVM内存泄漏专题实战

通览该系列文章之后,对JVM会有一个整体的认识,对于JVM问题排查和优化会有一定的帮助,如果想对JVM有更深入的理解和认知,建议深入看一下这本书《Java虚拟机:JVM高级特性与最佳实践(最新第二版)》,网上可以找到pdf版的,大家可以自己百度一下。

你可能感兴趣的:(JVM原理及优化)