跳至主要內容

Java问题诊断和排查工具(查看JVM参数、内存使用情况及分析)

沉默王二Java核心Java虚拟机约 4781 字大约 16 分钟

Java 问题诊断和排查工具(查看 JVM 参数、内存使用情况及分析)

JDK 自带的工具

在 JDK 的 bin 目录下有很多命令行工具:

我们可以看到各个工具的大小基本上都稳定在 27kb 左右,这个不是 JDK 开发团队刻意为之的,而是因为这些工具大多数是 jdk\lib\tools.jar 类库的一层薄包装而已,他们的主要功能代码是在 tools 类库中实现的。

命令行工具的好处是:当应用程序部署到生产环境后,无论是直接接触物理服务器还是远程 telnet 到服务器上都会受到限制。而借助 tools.jar 类库里面的接口,我们可以直接在应用程序中实现功能强大的监控分析功能。

常用命令:

这里主要介绍如下几个工具:

1、jps:查看本机 java 进程信息

2、jstack:打印线程的信息,制作   线程 dump 文件

3、jmap:打印内存映射信息,制作   堆 dump 文件

4、jstat:性能监控工具

5、jhat:内存分析工具,用于解析堆 dump 文件并以适合人阅读的方式展示出来

6、jconsole:简易的 JVM 可视化工具

7、jvisualvm:功能更强大的 JVM 可视化工具

8、javap:查看字节码

JAVA Dump:

JAVA Dump 就是虚拟机运行时的快照,将虚拟机运行时的状态和信息保存到文件中,包括:

线程 dump:包含所有线程的运行状态,纯文本格式

堆 dump:包含所有堆对象的状态,二进制格式

1、jps

显示当前所有 java 进程 pid 的命令,我们可以通过这个命令来查看到底启动了几个 java 进程(因为每一个 java 程序都会独占一个 java 虚拟机实例),不过 jps 有个缺点是只能显示当前用户的进程 id,要显示其他用户的还只能用 linux 的 ps 命令。

执行 jps 命令,会列出所有正在运行的 java 进程,其中 jps 命令也是一个 java 程序。前面的数字就是进程的 id,这个 id 的作用非常大,后面会有相关介绍。

jps -help:

jps -l  输出应用程序 main.class 的完整 package 名或者应用程序 jar 文件完整路径名

jps -v  输出传递给 JVM 的参数

jps 失效

我们在定位问题过程会遇到这样一种情况,用 jps 查看不到进程 id,用 ps -ef | grep java 却能看到启动的 java 进程。

要解释这种现象,先来了解下 jps 的实现机制:

java 程序启动后,会在目录/tmp/hsperfdata_{userName}/下生成几个文件,文件名就是 java 进程的 pid,因此 jps 列出进程 id 就是把这个目录下的文件名列一下而已,至于系统参数,则是读取文件中的内容。

我们来思考下:如果由于磁盘满了,无法创建这些文件,或者用户对这些文件没有读的权限。又或者因为某种原因这些文件或者目录被清除,出现以上这些情况,就会导致 jps 命令失效。

如果 jps 命令失效,而我们又要获取 pid,还可以使用以下两种方法:

1、top | grep java
2、ps -ef |grep java

2、jstack

主要用于生成指定进程当前时刻的线程快照,线程快照是当前 java 虚拟机每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待。

3、jmap

主要用于打印指定 java 进程的共享对象内存映射或堆内存细节。

堆 Dump 是反映堆使用情况的内存镜像,其中主要包括系统信息、虚拟机属性、完整的线程 Dump、所有类和对象的状态等。一般在内存不足,GC 异常等情况下,我们会去怀疑内存泄漏,这个时候就会去打印堆 Dump。

jmap 的用法摘要:

1、jmap pid

打印的信息分别为:共享对象的起始地址、映射大小、共享对象路径的全程。

2、jmap -heap pid:查看堆使用情况

3、jmap -histo pid:查看堆中对象数量和大小

打印的信息分别是:序列号、对象的数量、这些对象的内存占用大小、这些对象所属的类的全限定名

如果是内部类,类名的开头会加上*,如果加上 live 子参数的话,如 jmap -histo:live pid,这个命名会触发一次 FUll GC,只统计存活对象

4、jmap -dump:format=b,file=heapdump pid:将内存使用的详细情况输出到文件

然后使用 jhat 命令查看该文件:jhat -port 4000 文件名 ,在浏览器中访问 http:localhost:4000/

总结:

该命令适用的场景是程序内存不足或者 GC 频繁,这时候很可能是内存泄漏。通过用以上命令查看堆使用情况、大量对象被持续引用等情况。

4、jstat

主要是对 java 应用程序的资源和性能进行实时的命令行监控,包括了对 heap size 和垃圾回收状况的监控。

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

option:我们经常使用的选项有 gc、gcutil

vmid:java 进程 id

interval:间隔时间,单位为毫秒

count:打印次数

1、jstat -gc PID 5000 20

S0C:年轻代第一个 survivor 的容量(字节)

S1C:年轻代第二个 survivor 的容量(字节)

S0U:年轻代第一个 survivor 已使用的容量(字节)

S1U:年轻代第二个 survivor 已使用的容量(字节)

EC:年轻代中 Eden 的空间(字节)

EU:年代代中 Eden 已使用的空间(字节)

OC:老年代的容量(字节)

OU:老年代中已使用的空间(字节)

PC:永久代的容量

PU:永久代已使用的容量

YGC:从应用程序启动到采样时年轻代中 GC 的次数

YGCT:从应用程序启动到采样时年轻代中 GC 所使用的时间(单位:S)

FGC:从应用程序启动到采样时老年代中 GC(FULL GC)的次数

FGCT:从应用程序启动到采样时老年代中 GC 所使用的时间(单位:S)

2、jstat -gcutil PID 5000 20

s0:年轻代中第一个 survivor 已使用的占当前容量百分比

s1:年轻代中第二个 survivor 已使用的占当前容量百分比

E:年轻代中 Eden 已使用的占当前容量百分比

O:老年代中已使用的占当前容量百分比

P:永久代中已使用的占当前容量百分比

5、jhat

主要用来解析 java 堆 dump 并启动一个 web 服务器,然后就可以在浏览器中查看堆的 dump 文件了。

生成 dump 文件的方法前面已经介绍了,这边主要介绍如何解析 java 堆转储文件,并启动一个 web server

jhat heapdump

这个命令将 heapdump 文件转换成 html 格式,并且启动一个 http 服务,默认端口为 7000。

如果端口冲突,可以使用以下命令指定端口:jhat -port 4000 heapdump

下面我们来访问下:ip:port

6、jinfo

jinfo 可以用来查看正在运行的 java 运用程序的扩展参数,甚至支持在运行时动态地更改部分参数。

基本使用语法如下: jinfo -< option > < pid > ,其中 option 可以为以下信息:

-flag< name >: 打印指定 java 虚拟机的参数值

-flag [+|-]< name >:设置或取消指定 java 虚拟机参数的布尔值

-flag < name >=< value >:设置指定 java 虚拟机的参数的值

使用示例

下面的命令显示了新生代对象晋升到老年代对象的最大年龄。在运行程序运行时并没有指定这个参数,但是通过 jinfo,可以查看这个参数的当前的值。

下面的命令显示是否打印 gc 详细信息:

下面的命令在运用程序运行时动态打开打印详细 gc 信息开关:

注意事项:jinfo 虽然可以在 java 程序运行时动态地修改虚拟机参数,但并不是所有的参数都支持动态修改。

7、jcmd

在 JDK 1.7 之后,新增了一个命令行工具 jcmd。它是一个多功能工具,可以用来导出堆,查看 java 进程,导出线程信息,执行 GC 等。jcmd 拥有 jmap 的大部分功能,Oracle 官方建议使用 jcmd 代替 jmap。

使用  jcmd -l  命令列出当前运行的所有虚拟机,示例:

针对每一个虚拟机,可以使用 help 命令列出该虚拟机支持的所有命令,示例:

子命令含义:

  • VM.native_memory
  • VM.commercial_features
  • GC.rotate_log
  • ManagementAgent.stop
  • ManagementAgent.start_local
  • ManagementAgent.start
  • Thread.print,                         打印线程栈信息
  • GC.class_histogram,              查看系统中类统计信息
  • GC.heap_dump,                    导出堆信息,与 jmap -dump 功能一样
  • GC.run_finalization,               触发 finalize()
  • GC.run,                                触发 gc()
  • VM.uptime,                           VM 启动时间
  • VM.flags,                              获取 JVM 启动参数
  • VM.system_properties,          获取系统 Properties
  • VM.command_line,                 启动时命令行指定的参数
  • VM.version
  • help

示例:

8、可视化监控工具(JConsole、JVisualVM)

简介

在 JDK 安装目录的 bin 文件夹下,除了提供有命令行监控工具外,还提供了几种可视化的监控工具,以方便用户直观地了解虚拟机的运行状态。常用的可视化监控工具如下:

JConsole

简介

JConsole(Java Monitoring and Management Console)是一款基于 JMX(Java Manage-ment Extensions)的可视化监视工具。它的主要功能是通过 JMX 的 MBean(Managed Bean)对系统信息进行收集和动态调整系统参数。JMX(Java Management Extensions)是一个为应用程序、设备、系统等植入管理功能的框架,通常用于监控系统的运行状态或管理系统的部分功能。

使用

打开位于 bin 目录下的 jconsole 程序后,它会自动扫描当前主机上的所有 JVM 进程:

选中需要监控的进程后,点击连接,即可进入监控界面。监控界面包含了 概览内存线程VM 概要MBean 六个选项卡。其中概览界面显示的是 内存线程 等三个选项卡界面的概览信息,如下所示:

而内存界面主要用于显示堆和非堆上各个区域的使用量:

线程界面内主要显示各个线程的堆栈信息,最下角有一个 检测死锁 按钮,点击后如果检测到死锁存在,则在下部的线程选项卡旁边会出现死锁选项卡:

点击死锁选项卡则可以看到造成死锁的线程:

最后的 选项卡主要用于显示当前已加载和已卸载的类的数量。而 VM 概要 选项卡则主要用于显示虚拟机的相关参数,如下所示:

VisualVM

简介

VisualVM(All-in-One Java Troubleshooting Tool)是 Oracle 提供的功能最强大的运行监视和故障处理程序之一, 它除了支持常规的运行监视、故障处理等功能外,还能用于性能分析(Profiling)。同时因为 VisualVM 是基于 NetBeans 平台的开发工具,所以它还支持通过插件来进行功能的拓展。VisualVM 的主要功能如下:

  • 显示虚拟机进程及其配置信息、环境信息(与 jps、jinfo 功能类似);
  • 监视应用程序的处理器、垃圾收集、堆、方法区以及线程的信息(与 jstat、jstack 功能类似);
  • dump 以及分析堆转储快照(与 jmap、jhat 功能类似);
  • 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法;
  • 离线程序快照:可以收集程序的运行时配置、线程 dump、内存 dump 等信息来建立快照。

使用

打开位于 bin 目录下的 jvisualvm 程序, 它会自动扫描当前主机上的所有 JVM 进程:

点击需要监控的进程后,右侧即会显示相关的监控信息:

1. 堆 Dump

在监控界面点击按钮可以 执行垃圾回收 或者 堆 Dump 。进行堆 Dump 后,还会显示其分析结果:

2. 线程 Dump

在线程界面可以查看所有线程的状态,如果出现死锁,该界面还会进行提示:

此时可以进行 线程 Dump 来获取具体的线程信息,效果和 jstack 命令类似:

3. 性能分析

在 Profiler 界面,可以进行 CPU 和 内存的性能分析。要开始性能分析,需要先选择 CPU内存 按钮中的一个,VisualVM 将会开始记录应用程序执行过的所有方法:如果是进行的是 CPU 执行时间分析,将会统计每个方法的执行次数、执行耗时;如果是内存分析,则会统计每个方法关联的对象数以及这些对象所占的空间。想要结束性能分析,点击停止按钮即可:

4. Visual GC

Visual GC 面板默认是不显示的,需要通过插件进行扩展。它会实时监控虚拟机的状态,在功能上类似于 jstat 命令:

安装插件

在主界面,点击 工具 => 插件 ,可以打开插件面板。右击插件选项或者点击安装按钮即可完成对应插件的安装:

需要注意的是,安装插件前需要按照自己 JVM 的版本来配置插件中心,否则会抛出 ”无法连接到插件中心“ 的异常。每个版本对应的插件中心可以在该网址上查看:https://visualvm.github.io/pluginscenters.html,界面如下:open in new window

之后需要将正确的插件中心的地址配置到程序中:

连接远程进程

以上演示 JConsole 和 VisualVM 时,我们都是用的本地进程,但在实际开发中,我们更多需要监控的是服务器上的远程进程。想要监控远程主机上的进程,需要进行 JMX 的相关配置,根据连接时是否需要用户名和密码,可以分为以下两种配置方式:

不使用安全凭证

启动服务器上的 Java 进程时增加以下参数:

 java -Dcom.sun.management.jmxremote.port=12345  #jmx远程连接的端口号
 -Dcom.sun.management.jmxremote.ssl=false
 -Dcom.sun.management.jmxremote.authenticate=false
 -jar springboot.jar

此时只需要知道主机地址和端口号就可以连接,不需要使用用户名和密码,所以安全性比较低。

使用安全凭证

启动服务器上的 Java 进程时增加以下参数:

java -Dcom.sun.management.jmxremote.port=12345
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.access.file=/usr/local/jmxremote.access
-Dcom.sun.management.jmxremote.password.file=/usr/local/jmxremote.password
-jar springboot.jar

其中 jmxremote.access 的内容如下,其中 admin 为用户名,readwrite 表示可读可写,也可以设置为 readonly(只读):

admin readwrite

jmxremote.password 的内容如下,其中 admin 为用户名,123456 为密码:

admin 123456

两个文件创建好后,还需要赋予其执行权限:

chmod 600 /usr/local/jmxremote.access
chmod 600 /usr/local/jmxremote.password
chown root:root /usr/local/jmxremote.access
chown root:root /usr/local/jmxremote.password

之后在使用 VisualVM 进行远程连接时,配置如下:

需要注意的是这里的端口号是配置的 Dcom.sun.management.jmxremote.port 的值,而不是 Java 程序的端口号。连接完成后,即可查看到对应进程的监控状态。

其他工具

JOL(即 Java Object Layout):OpenJDK 提供的库,用于查看 Java 对象的内存布局,这个很有用,可以借助它来跟踪锁升级等过程。只需要引入 Maven 即可使用,示例:

//引入依赖
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.16</version>
        </dependency>

//代码

class TTTT {
    public static void main(String[] args) {
        System.err.println(ClassLayout.parseInstance(new Person()).toPrintable());
        System.err.println(ClassLayout.parseClass(Person.class).toPrintable());
    }
}

class Person {
    private int age = 1;
    private String name = "zhangsan";
}

//代码执行结果
com.marchon.learning.Person object internals:
OFF  SZ               TYPE DESCRIPTION               VALUE
  0   8                    (object header: mark)     0x0000005e4c804101 (hash: 0x5e4c8041; age: 0)
  8   4                    (object header: class)    0xf8010dd9
 12   4                int Person.age                1
 16   4   java.lang.String Person.name               (object)
 20   4                    (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

openJDK 源码:查看 JDK native 方法的实现

strace:跟踪程序运行过程发起的系统调用

https://fastthread.ioopen in new window:线程栈分析的网站

上问题排查思路(八股)

硬盘使用情况:du 命令

内存使用且情况:free 命令

CPU 使用情况:top 命令

网络使用情况:netstat 命令

Java 程序问题分析:jmap 分析堆内存、jstack 分析线程栈等,见前文。


GitHub 上标星 10000+ 的开源知识库《二哥的 Java 进阶之路open in new window》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:太赞了,GitHub 上标星 10000+ 的 Java 教程open in new window

微信搜 沉默王二 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 222 即可免费领取。