2675 2019-12-20 2020-06-25
前言:当服务端出现不稳定或异常情况的时候,本篇可能能帮上不小的忙。
一、基本命令
给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。我们先来看下JDK提供的命令工具。
1、jps
jps(JVM Process Status Tool),显示指定系统内所有的虚拟机进程。一般运行jps -l即可满足大部分需求。
jps [ options ] [ hostid ]
[root@izwz93pwxi9qhdp46ajo0qz ~]# jps
13269 jar
3132 Jps
[root@izwz93pwxi9qhdp46ajo0qz ~]# jps -l
13269 ROOT.jar
3173 sun.tools.jps.Jps
[root@izwz93pwxi9qhdp46ajo0qz ~]# jps -v
3204 Jps -Dapplication.home=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.201.b09-2.el7_6.x86_64 -Xms8m
13269 jar
这个查出PID与系统进程PID是一致的,因此可以直接kill pid。下面介绍的命令也需要通过jps率先查出java进程id,然后进行其他的操作。
这里介绍一个实用的linux命令:tee -- read from standard input and write to standard output and files。用法如下
# 将当前屏幕输出转存一份进指定文件
[root@izwz93pwxi9qhdp46ajo0qz ~]# jps | tee test.txt
7652 Jps
5541 jar
2、jstat
jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或远程虚拟机进程中的类加载、内存、垃圾收集、JIT编译等运行数据。在没有GUI图形界面的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。
选项 | 作用 |
---|---|
-class | 监视类装载、卸载数量、总空间以及装载所耗费的时间 |
-gc | 监视Java堆状况,包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息 |
-gccapacity | 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间 |
-gcutil | 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause | 与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因 |
-gcnew | 监视新生代GC状况。-gcnewcapacity,主要关注使用到的最大、最小空间 |
-gcold | 监视老年代GC状况。-gcoldcapacity,主要关注使用到的最大、最小空间 |
-gcpermcapacity | 输出永久代使用到的最大、最小空间 |
-compiler | 输出JIT编译器编译过的方法、耗时情况 |
-printcompilation | 输出已经被JIT编译的方法 |
以下是实例演示
# 命令格式 vmid代表虚拟机进程id,interval和count代表查询间隔和次数(默认1)
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
# 用idea启动了tomcat
hk@hk-LPC:/usr/share/man$ jps -l
8357 sun.tools.jps.Jps
6554 com.intellij.idea.Main
8030 org.apache.catalina.startup.Bootstrap
8015 org.jetbrains.jps.cmdline.Launcher
hk@hk-LPC:/usr/share/man$ jstat -class 8030
Loaded Bytes Unloaded Bytes Time
2579 4974.3 0 0.0 2.85
# s0c = survivor0 capacity, s0u = survivor0 used, 7680k = 7.5M
# EC = Eden capacity, EU = Eden Used, OU = Old Used, 48640k = 47.5M, 128512k = 125.5M
# MC = Method/Perm Capacity, 16256k = 15.875M, 15566.4k = 15.2M
# CCSC = Compress Class Capacity
# YGC = 年轻代垃圾回收次数
# YGCT = 年轻代垃圾回收消耗时间
# FGC = 老年代垃圾回收次数(major GC和full GC通常是等价的,因为major GC通常由minor GC触发)
# FGCT = 老年代垃圾回收消耗时间
# GCT = 垃圾回收消耗总时间
hk@hk-LPC:/usr/share/man$ jstat -gc 8030
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
7680.0 7680.0 6175.5 0.0 48640.0 9598.1 128512.0 96.0 16256.0 15566.4 1920.0 1695.6 2 0.018 0 0.000 0.018
3、jinfo
jinfo(Configuration Info for Java),显示/调整虚拟机配置信息。
选项 | 作用 |
---|---|
no option | 输出全部的参数和系统属性 |
-flag name | 输出对应名称的参数 |
-flag [+|-]name | 开启或者关闭对应名称的参数(如果可以) |
-flag name=value | 设定对应名称的参数(如果可以) |
-flags | 输出全部的参数 |
-sysprops | 输出系统属性 |
以下是实例演示
# 命令格式
jinfo [option] <pid>
# 某些命令可能并不能正确执行
[root@izwz93pwxi9qhdp46ajo0qz ~]# jinfo -flags 13269
Attaching to process ID 13269, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.201-b09
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=482344960 -XX:MaxNewSize=160759808 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=10485760 -XX:OldSize=20971520 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
Command line:
[root@izwz93pwxi9qhdp46ajo0qz ~]# jinfo -flag NewSize 13269
-XX:NewSize=10485760
4、jmap
jmap(Memory Map for Java),生成虚拟机的内存转储快照(headdump文件)。
选项 | 作用 |
---|---|
-dump | 生成Java堆转储快照。格式为:-dump:[live, ]format=b, file=<filename>,其中live子参数说明是否只dump出存活的对象 |
-finalizerinfo | 显示在F-Queue中等待Finalizer线程执行finalize方法的对象 |
-heap | 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等 |
-histo | 显示堆中对象统计信息,包括类、实例数量、合计数量 |
-permstat | 以ClassLoader为统计口径显示永久代内存状态 |
-F | 当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照 |
以下是实例演示
# 命令格式
jmap [option] <pid>
hk@hk-LPC:~$ jmap -dump:live,format=b,file=dump.bin 11086
Dumping heap to /home/hk/dump.bin ...
Heap dump file created
[root@izwz93pwxi9qhdp46ajo0qz ~]# jmap -heap 13269
Attaching to process ID 13269, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.201-b09
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 482344960 (460.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 160759808 (153.3125MB)
OldSize = 20971520 (20.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
5、jhat
jhat(Java Heap Analysis Tool),用于分析生成的heapdump文件。它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果。由于分析是一项比较耗时而且消耗硬件的过程,一般做法是将服务器dump文件复制下来并采用第三方专业工具来分析。
# 大致了解即可
hk@hk-LPC:~$ jhat dump.bin
Reading from dump.bin...
Dump file created Thu Dec 12 10:11:30 CST 2019
Snapshot read, resolving...
Resolving 128505 objects...
Chasing references, expect 25 dots.........................
Eliminating duplicate references.........................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
6、jstack*
jstack(Stack Trace for Java),用于生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件)。线程快照就是当前虚拟机每一条线程正在执行的方法堆栈的集合,用来定位线程出现长时间停顿的原因。
选项 | 作用 |
---|---|
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用本地方法的话,可以显示C/C++的堆栈 |
实例演示如下
# 命令格式
jstack [option] vmid
hk@hk-LPC:~$ jstack 11086
2019-12-12 10:36:14
Full thread dump OpenJDK 64-Bit Server VM (25.181-b13 mixed mode):
"Attach Listener" #60 daemon prio=9 os_prio=0 tid=0x00007f7b10001000 nid=0x2f14 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"ajp-nio-8009-AsyncTimeout" #52 daemon prio=5 os_prio=0 tid=0x00007f7b50625800 nid=0x2b9a waiting on condition [0x00007f7b2e6e5000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.coyote.AbstractProtocol$AsyncTimeout.run(AbstractProtocol.java:1154)
at java.lang.Thread.run(Thread.java:748)
"ajp-nio-8009-Acceptor-0" #51 daemon prio=5 os_prio=0 tid=0x00007f7b50623800 nid=0x2b99 runnable [0x00007f7b2e7e6000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
- locked <0x00000007060260f8> (a java.lang.Object)
at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:482)
at java.lang.Thread.run(Thread.java:748)
# 省略其他
当定位线上问题时,这个命令应该率先被使用,这样我们可以清楚的知道问题大致发生在哪个地方以及大致原因。
二、可视化工具-jconsole
主要是以JConsole(jconsole)和VisualVM(jvisualvm)为主,由于jconsole是各版本JDK都会有的,而其他的却不一定有,这里以jconsole为例进行演示。
1、远程连接jvm
需要加入以下参数
# 如果是以tomcat为载体,则需要在tomcat的catalina.sh中加入
# 如果是以java -jar启动,则需要在后面补上这些参数
-Djava.rmi.server.hostname=120.79.20.49
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8076
-Dcom.sun.management.jmxremote.rmi.port=8077
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
# 下面是一个例子
java -jar -Djava.rmi.server.hostname=120.79.20.49 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7076 -Dcom.sun.management.jmxremote.rmi.port=7077 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false ROOT.jar --spring.profiles.active=local
2、查看当前垃圾收集器
# 本地环境,使用Parrallel Scavenge + Parallel Old收集器组合,注重吞吐量以及对CPU资源敏感
hk@hk-LPC:/usr/lib/jvm/java-8-openjdk-amd64/bin$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=195128768 -XX:MaxHeapSize=3122060288 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)
# 远程服务器环境
# 上面命令绝大部分下可以直接查看到所使用的,但不排除例外,此时可以采用下面命令
# java -XX:+PrintFlagsFinal -version | grep Use | grep true
[root@izwz93pwxi9qhdp46ajo0qz xiaokui]# java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=30113344 -XX:MaxHeapSize=481813504 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
openjdk version "1.8.0_201"
OpenJDK Runtime Environment (build 1.8.0_201-b09)
OpenJDK 64-Bit Server VM (build 25.201-b09, mixed mode)
其他关于图形界面的操作就不在多做介绍了。
三、理论调优
案例均精裁剪自周志明 - 《深入理解Java虚拟机》。
1、Java堆过大
具体案例如下:Java堆过大,且产生了许多大对象,这些大对象大部分直接进入了老年代,避过了Minor GC。长时间后导致Java堆内存不足,触发Major GC,一次Major GC可高达10秒。对外表现为后台服务器卡死。
解决思路(抛开代码层面):
- 如果不嫌硬件浪费,可以直接缩小Java堆的大小,外加提高对象直接进入老年代的门槛,这样可以保证Minor GC能够回收一部分大对象,Major GC次数减少且回收对象数量/大小降低。
- 如果不想浪费硬件资源,就采用集群部署 + 负载均衡,单个机器开多个虚拟机进程,每个进程大致优化思路同上。
- 抛开具体的业务场景和最新的垃圾收集器(假设限定JDK8),如果后台需要长时间数据运算,则可以采用注重吞吐量(垃圾收集时间占总时间比例)的Parallel Scavenge + Parallel Old组合(+XX:UseParallelGC)。如果后台需要快速响应,追求低延迟,那么注重低延迟的ParNew + CMS + Serial Old(作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用)组合是个不错的选择(+XX:UseConcMarkSweepGC)。
- 如果JDK想体验最新垃圾收集器的研究成果,那么G1(目标是替代CMS)、Shenandoah、ZGC是个不错的选择。
2、代码导致的GC问题
一般来说可能由以下几种情况导致:
- 堆内存过小,堆内存快速填满,GC过于频繁,表现为系统吞吐量降低。堆内存过大,长时间后堆内存不足,单次GC耗时较长,表现为系统假死。
- 非堆内存异常,这时也可能抛出java.lang.OutOfMemoryError。可能出问题原的区域有:直接内存、线程堆栈、Socket缓存区、JNI代码。
- 内部线程可能导致的一系列问题,有待完善。
3、打印GC日志
下面是一些常用的打印GC参数:
选项 | 说明 |
---|---|
-XX:+PrintGCTimeStamps | 打印GC停顿时间 |
-XX:+PrintGCDetails | 打印GC详细信息 |
-verbose:gc | 打印GC信息,输出内容已被前一个参数包括 |
-Xloggc:gc.log | 将GC日志转储至文件 |
四、调优实战一
有待完善。
总访问次数: 427次, 一般般帅 创建于 2019-12-20, 最后更新于 2020-06-25
欢迎关注微信公众号,第一时间掌握最新动态!