在JDK的bin目录下有很多命令行工具,命令行工具的好处是:当应用程序部署到生产环境后,无论是直接接触物理服务器还是远程telnet到服务器上都会受到限制。而借助tools.jar类库里面的接口,我们可以直接在应用程序中实现功能强大的监控分析功能。
- jps:查看本机java进程信息
- jstack:打印线程的栈信息,制作 线程dump文件
- jmap:打印内存映射信息,制作 堆dump文件
- jstat:性能监控工具
- jhat:内存分析工具,用于解析堆dump文件并以适合人阅读的方式展示出来
- jconsole:简易的JVM可视化工具
- jvisualvm:功能更强大的JVM可视化工具
- javap:查看字节码
JAVA Dump:就是虚拟机运行时的快照,将虚拟机运行时的状态和信息保存到文件中,包括:
- 线程dump:包含所有线程的运行状态,纯文本格式
- 堆dump:包含所有堆对象的状态,二进制格式
jps
显示当前所有java进程pid的命令,我们可以通过这个命令来查看到底启动了几个java进程(因为每一个java程序都会独占一个java虚拟机实例),不过jps有个缺点是只能显示当前用户的进程id,要显示其他用户的还只能用linux的ps命令。
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
jstack
主要用于生成指定进程当前时刻的线程快照,线程快照是当前java虚拟机每一条线程正在执行的方法堆栈的集合。比如我们定位线上cupu飙高,
- 先用top找出占据cpu的java进程的pid,
- 然后再使用jstack ${pid} > a.txt,此时txt保存的都说16进制的
- 然后使用top -Hp ${pid},会展现但是此时线程是十进制,找出那个占据cpu最高的线程,需要转换成16进制,可以使用 printf "%x" ${threadId} 我这里是430224, 得到十六进制的69090
- 使用vi命令进入命令模式,输入:/69090 查看对应的堆栈或者sz a.txt下载到本地桌面,用记事本打开a.txt找到对应线程id 69090
jmap
主要用于打印指定java进程的共享对象内存映射或堆内存细节。堆Dump是反映堆使用情况的内存镜像,其中主要包括系统信息、虚拟机属性、完整的线程Dump、所有类和对象的状态等。一般在内存不足,GC异常等情况下,我们会去怀疑内存泄漏,这个时候就会去打印堆Dump。
- jmap -heap pid:查看堆使用情况
- jmap -histo pid:查看堆中对象数量和大小
打印的信息分别是:序列号、对象的数量、这些对象的内存占用大小、这些对象所属的类的全限定名。如果是内部类,类名的开头会加上*,如果加上live子参数的话,如jmap -histo:live pid,这个命名会触发一次FUll GC,只统计存活对象
- jmap -dump:format=b,file=heapdump pid:将内存使用的详细情况输出到文件
然后使用jhat命令查看该文件:jhat -port 4000 文件名 ,在浏览器中访问http:localhost:4000/ 不过还是建议使用jdk自带的jvisuavm该命令适用的场景是程序内存不足或者GC频繁,这时候很可能是内存泄漏。通过用以上命令查看堆使用情况、大量对象被持续引用等情况。但是执行此命令会导致程序触发stop the word短暂暂停
jstat
主要是对java应用程序的资源和性能进行实时的命令行监控,包括了对heap size和垃圾回收状况的监控。jstat -
- 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
可视化监控工具JVisualVM
其实还有个JConsole工具,但是jvisualVM功能比它更多更强大,所以主要了解它即可
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 命令:
安装插件
在主界面,点击 工具 => 插件 ,可以打开插件面板。右击插件选项或者点击安装按钮即可完成对应插件的安装:
连接远程进程
以上演示 JConsole 和 VisualVM 时,我们都是用的本地进程,但在实际开发中,我们更多需要监控的是服务器上的远程进程。想要监控远程主机上的进程,需要进行 JMX 的相关配置,根据连接时是否需要用户名和密码,可以分为以下两种配置方式:
#不使用安全凭证
启动服务器上的 Java 进程时增加以下参数:或者把它配到jvm参数配置中
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 程序的端口号。连接完成后,即可查看到对应进程的监控状态
参考文档:Java问题诊断和排查工具(查看JVM参数、内存使用情况及分析) | 二哥的Java进阶之路