46

java应用监测(3)-这些命令行工具你掌握了吗

 4 years ago
source link: https://www.tuicool.com/articles/3EBrIzr
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

tags: java, troubleshooting, monitor,jvm

一句话概括:原来jdk自带的命令行工具如此好用,本文将详细介绍。

1 引言

监测java应用,最方便的就是直接使用jdk提供的现成工具,在jdk的安装的bin目录下,已经提供了多种命令行监测工具,以便于开发人员和运维人员监测java应用和诊断问题,因此,此类工具是java应用监测的重要手段。也是作为java开发人员需要掌握的基本技能。

2 常用监测命令行工具

一般来说,常用的命令行工具包括 jps , jinfo , jmap , jstack , jstat ,这些工具都在 JAVA_HOME/bin/ 目录下,概要说明如下:

  • jps 查看java进程ID

  • jinfo 查看及调整虚拟机参数

  • jmap 查看堆(heap)使用情况及生成堆快照

  • jstack 查看线程运行状态及生成线程快照

  • jstat 显示进程中的类装载、内存、垃圾收集等运行数据。

通过这些工具,基本上可以了解java应用的内存变化状态,线程运行状态等信息,进而为应用监测及问题诊断提供依据。下面将结合实例对这些工具的使用进行详细讲解,文中所使用的示例代码 java-monitor-example 已上传到我的github,地址: https://github.com/mianshenglee

3 进程查询工具jps

3.1 jps说明

要监测java应用,第一步就是先知道这个应用是哪个进程,它的运行参数是什么。 jps 就是可以查询进程的工具。熟悉linux的同学,大概都知道查询进程使用 ps-ef|grep java 这样的命令,jps也类似,但它不使用名称查找,而是查找全部当前jdk运行的java进程,而且只查找当前用户的Java进程,而不是当前系统中的所有进程。

3.2 jps使用

作为命令行工具,可以通过 -help 参数查看帮助,也可查阅官方文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jps.html ,如下:

示例工程 java-monitor-example 在linux机器中运行起来,使用 jps 可输出以下信息:

  • 只输出进程ID

  • 输出程序完整名称及JVM参数

输出的内容中, java-monitor-example-0.0.1-SNAPSHOT.jar-l 输出的完整名称, -Xms128m-Xmx128m-Dserver.port=8083 是传给JVM的参数。

  • 在shell脚本中使用命令获取java进程ID并作为变量使用

上述脚本,比较适合运维人员对应用的开启和关闭,自动获取java进程ID,然后根据ID判断程序是否运行(start),或者关闭应用( kill-9 )。

4 配置信息工具jinfo

4.1 jinfo说明

知道java应用所属的进程号是第一步,在上一篇文章《java应用监测(2)-java命令的秘密》中,已经知道java的启动参数有很多,监测java应用前需要了解清楚它的启动参数是什么。这时就需要用到 jinfo 工具。 jinfo 可以输出JAVA应用的系统参数和JVM参数。jinfo还能够修改一部分运行期间能够调整的虚拟机参数,很多运行参数是不能调整的,如果出现"cannot be changed"异常,说明不能调整。不过官方文档指出,这个命令在后续的版本中可能不再使用,当前JDK8还是可以用的。

4.2 jinfo使用

通过 -help 参数查看帮助,也可查阅 jinfo 官方文档说明: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jinfo.html ,如下:

使用 jps 获取到应用的进程ID后(示例的PID为13680),如果直接 jps<pid> 则会输出全部的系统参数和JVM参数,其它参数说明在 help 中也说得很清楚了。下面还是结合示例代码 java-monitor-example 来实践一下:

  • 获取java应用的堆初始值

  • 查看全部的JVM参数

可见,由于我们启动时设置了 -Xms-Xmx ,它们对应的就是 -XX:InitialHeapSize-XX:MaxHeapSize 值。另外,参数 -Dserver.port 属于系统参数,使用 jinfo-sysprops13680 就可以查看系统参数了。

5 堆内存查看工具jmap

5.1 jmap说明

java应用启动后,它在JVM中运行,内存是需要重点监测的地方, jmap 就是这样的一个工具,它可以获取运行中的jvm的堆的快照,包括整体情况,堆占用情况的直方图,dump出快照文件以便于离线分析等。官方文档指出,这个命令在后续的版本中可能不再使用,当前JDK8还是可以用的。

5.2 jmap使用

通过 -help 参数查看帮助,也可查阅 jmap 官方文档说明: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jmap.html ,帮助说明如下:

如上所示, jmap 参数常用的是 -heap , -histo-dump ,结合示例 java-monitor-example ,说明如下:

  • 打印jvm内存整体使用情况

从以上信息,可以看出JVM中堆内存当前的使用情况,包括年轻代( Eden 区, From 区, To 区)和年老代。

  • 查看类名,对象数量,对象占用大小直方图

如上所示,使用 -histo 输出包括序号,实例数,占用字节数和类名称。具体说明如下:

  1. instances列:表示当前类有多少个实例。

  2. bytes列:说明当前类的实例总共占用了多少个字节

  3. class name列:表示的就是当前类的名称,class name 解读:

  4. B代表byte

  5. C代表char

  6. D代表double

  7. F代表float

  8. I代表int

  9. J代表long

  10. Z代表boolean

  11. [代表数组,如[I相当于int[]

  12. 对象用[L+类名表示

  • 把内存情况dump内存到本地文件

如上所示,会把堆情况写入到当前目录的 heap.hprof 文件中,至于如何分析此文件,可以使用 jhat ,但一般实际开发中,很少使用jhat来直接对内存dump文件进行分析,因此不再对它进行讲述。更多的是使用工具 MAT ,以可视化的方式来查看,后续文章将会对 MAT 工具的使用进行详细讲解。

6 线程栈查询工具jstack

6.1 jstack说明

此命令打印指定Java应用的线程堆栈,对于每个Java帧,将打印完整的类名,方法名,字节代码索引(BCI)和行号,可以用于检测死锁,线程停顿,进程耗用cpu过高报警问题等排查。

6.2 jstack使用

使用 -help 参数查看帮助,也可查阅 jstack 官方文档说明: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstack.html ,帮助说明如下:

结合示例 java-monitor-example ,可以打印线程信息(一般都会把打印的内容写入到文件然后再分析),如下:

  • 打印当前线程堆栈信息

6.3 线程dump分析

6.3.1 线程状态

java线程栈使用 jstack dump出来后,可以看到线程的状态,线程状态一共分6种,可以参考官方文档: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr034.html ,下面是它的状态说明:

  • NEW

线程已经new出来创建了,但是还没有启动(not yet started), jstack 不会打印这个状态的线程信息。

  • RUNNABLE

正在Java虚拟机下跑任务的线程的状态,但其实它是只是表示线程是可运行的(ready)。对于单核的CPU,多个线程在同一时刻,只能运行一个线程,其它的则需要等CPU的调度。

  • BLOCKED

线程处于阻塞状态,正在等待一个锁,多个线程共用一个锁,当某线程正在使用这个锁进入某个synchronized同步方法块或者方法,而此线程需要进入这个同步代码块,也需要这个锁,则导致本线程处于阻塞状态。

  • WAITING

等待状态,处于等待状态的线程是由于执行了3个方法中的任意方法。1. Object.wait 方法,并且没有使用timeout参数; 2. Thread.join 方法,没有使用timeout参数 3. LockSupport.park 方法。处于waiting状态的线程会等待另外一个线程处理特殊的行为。一个线程处于等待状态(wait,通常是在等待其他线程完成某个操作(notify或者notifyAll)。注意, Object.wait() 方法只能够在同步代码块中调用。调用了 wait() 方法后,会释放锁。

  • TIMED_WAITING

线程等待指定的时间,对于以下方法的调用,可能会导致线程处于这个状态:1. Thread.sleep方法 2. Object.wait 方法,带有时间 3. Thread.join 方法,带有时间 4. LockSupport.parkNanos 方法,带有时间 5. LockSupport.parkUntil 方法,带有时间。注意, Thread.sleep 方法调用后,它不会释放锁,仍然占用系统资源。

  • TERMINATED

线程中止的状态,这个线程已经完整地执行了它的任务。

从下面这张图可以看出线程状态的变化情况:

emMb6nB.jpg!web

6.3.2 分析jstack后线程栈内容

从前面使用 jstack dump出来信息,我们需要知道以下几个信息:

  • "http-nio-8083-Acceptor-0"#39 :是线程的名字,因此,一般我们创建线程时需要设置自己可以辩识的名字。

  • daemon 表示线程是否是守护线程

  • prio 表示我们为线程设置的优先级

  • os_prio 表示的对应的操作系统线程的优先级,由于并不是所有的操作系统都支持线程优先级,所以可能会出现都置为0的情况

  • tid 线程的id

  • nid 线程对应的操作系统本地线程id,每一个java线程都有一个对应的操作系统线程,它是16进制的,因此一般在操作系统中获取到线程ID后,需要转为16进制,来对应上。

  • java.lang.Thread.State:RUNNABLE 运行状态,上面已经介绍了线程的状态,若是WAITING状态,则括号中的内容说明了导致等待的原因,如parking说明是因为调用了LockSupport.park方法导致等待。通常的堆栈信息中,都会有lock标记,如  -locked<0x00000000f8c85380>(a java.lang.Object) 表示正在占用这个锁。

  • 对于线程停顿,CPU占用等问题,可以重点看一下  wait 状态的线程

  • 对于死锁,在Dump出来的线程栈快照可以直接报告出Java级别的死锁。

7 JVM统计数据工具jstat

7.1 jstat说明

jstatJVMStatisticsMonitoringTool ,即JVM统计监测工具,包括监测类装载、内存、垃圾收集、JIT编译等运行数据,在没有图形的服务器上,它是运行期定位虚拟机性能问题的首选工具。

7.2 jstat使用

使用 -help 参数查看帮助,也可查阅 jstat 官方文档说明: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html ,帮助说明如下:

以上所示, vmidintervalcount 分别是进程号,打印间隔时间(s或ms),打印次数,其中 option 参数主要是以下(也可以使用命令 jstat-option 查看):

  • -class 统计class loader行为信息 ,如总共加载了多少个类

  • -compile 统计HotSpot Just-in-Time编译器的行为

  • -gc 统计jdk gc时heap信息

  • -gccapacity 统计不同的generations相应的heap容量情况

  • -gccause 统计gc的情况,(同-gcutil)和引起gc的事件

  • -gcnew 统计gc时,新生代的情况

  • -gcnewcapacity 统计gc时,新生代heap容量

  • -gcold 统计gc时,老年区的情况

  • -gcoldcapacity 统计gc时,老年区heap容量

  • -gcpermcapacity 统计gc时,permanent区heap容量

  • -gcutil 统计gc时,heap情况

  • -printcompilation hotspot编译方法统计

一般我们使用 -class-gc-gccause-gcutil 比较多,主要用于来分析类和堆使用情况及gc情况。

7.3 监测JVM的GC情况

以上述的示例工程 java-monitor-example 为例,里面包含了一个函数来测试内存溢出(使用一个数组,循环创建对象,直到内存溢出)。使用 jstat-gc136801000 即每秒监测一次,调用 /monitor/user/oom 接口后,即看到堆和GC变化情况。为方便查看,我把输出放到sublime中显示,如下所示:

yqq6J3V.jpg!web

日志输出OOM报错:

q6FjMvj.jpg!web

以上输出的内容,每列的说明如下:

  • S0C 当前年轻代中第一个survivor(s0)的总容量 (KB).

  • S1C 当前年轻代中第一个survivor(s1)的总容量 (KB).

  • S0U s0已使用的容量 (KB).

  • S1U s1已使用的容量 (KB).

  • EC 当前年轻代中eden区总容量 (KB).

  • EU eden区已经使用的容量 (KB).

  • OC 年老代的容量总容量 (KB).

  • OU 年老代已使用容量(KB).

  • MC 当前 Metaspace总容量(KB).

  • MU 当前 Metaspace已使用容量 (KB).

  • CCSC Compressed class容量大小

  • CCSU Compressed class已使用容量

  • YGC 从应用启动时到现在,年轻代young generation 发生GC Events的总次数.

  • YGCT 从应用启动时到现在, 年轻代Young generation 垃圾回收的总耗时.

  • FGC 从应用启动时到现在, full GC事件总次数.

  • FGCT 从应用启动时到现在, Full sc总耗时.GCT 从应用启动时到现在, 垃圾回收总时间. -  GCT GCT=YGCT+FGCT

从以上输出第6行可以看出, ECEUOCOU 表示年轻代、年老代的内存都已经用完(与容量数值相等),发生OOM。这时,则需要采取措施,增大内存(-Xmx参数)或者找到导致OOM的代码进行修改。

8 总结

针对java应用的监测,本文对jdk提供自身提供的命令行工具进行了说明和使用的介绍,完整的描述了查看java应用进程,查看启动参数,查看内存情况,查看线程情况,查看内存统计情况等,主要是 jpsjinfojmapjstackjstat 5个工具,并结合实例,希望学习java开发人员都能掌握这些技术,在监测java应用时,可以从容面对如OOM,CPU高,线程停顿等问题。

参考资料

  • JDK工具参考文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/

  • 示例代码  java-monitor-example :  https://github.com/mianshenglee/my-example/tree/master/java-monitor-example

  • Java开发必须掌握的线上问题排查命令: https://www.hollischuang.com/archives/1561

  • Java自带的性能监测工具: http://www.tianshouzhi.com/api/tutorials/jvm/346

相关阅读


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK