文章目录
概述
- 体会1:使用数据说明问题,使用知识分析问题,使用工具处理问题。
- 体会2:无监控、不调优!
官方源码地址:http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jcmd/share/classes/sun/tools
jps:查看正在运行的Java进程
jps(Java Process Status):显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。
说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的
基本使用语法为:jps [options] [hostid]
我们还可以通过追加参数,来打印额外的信息。
options参数
- -q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等
- -l:输出应用程序主类的全类名 或 如果进程执行的是jar包,则输出jar完整路径
- -m:输出虚拟机进程启动时传递给主类main()的参数
- -v:列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的jvm参数。
说明:以上参数可以综合使用。
补充:如果某 Java 进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java 进程。
hostid参数
RMI注册表中注册的主机名。如果想要远程监控主机上的 java 程序,需要安装 jstatd。
对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。
如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具
实战
/**
* @author zhangjianbin
* @date 2021年07月21日21:56
*/
public class ScannerTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
}
}
查看jvm的pid
查看jvm已设置的参数
jstat:查看JVM统计信息
jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具
。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
常用于检测垃圾回收问题以及内存泄漏问题
官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
基本使用语法为:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
查看命令相关参数:jstat-h 或 jstat-help
-
其中vmid是进程id号,也就是jps之后看到的前面的号码
-
interval是采样时间间隔,单位为毫秒。count是采样总次数
-
t参数: 可以在输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒
-
-h参数: 可以在周期性数据输出时,输出多少行数据后输出一个表头信息
jstat还可以用来判断是否出现内存泄漏
/**
* -Xms60m -Xmx60m -XX:SurvivorRatio=8
*
* @author zhangjianbin
* @date 2021年07月21日22:27
*/
public class GcTest {
public static void main(String[] args) {
ArrayList<byte[]> arrayList = new ArrayList();
for (int i = 0; i < 1000; i++) {
byte[] arr = new byte[1024 * 100];//100KB
arrayList.add(arr);
try {
Thread.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
-
第1步:在长时间运行的 Java 程序中,
我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中 OU 列(即已占用的老年代内存)的最小值
。 -
第2步:然后,
我们每隔一段较长的时间重复一次上述操作,来获得多组 OU 最小值。如果这些值呈上涨趋势
,则说明该 Java 程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏
。
option参数
选项option可以由以下值构成。
类装载相关的
:
-class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
垃圾回收相关的
:
-gc:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。
C即Capacity 总容量,U即Used 已使用的容量,容量的单位都是 字节
S0C : survivor0区的总容量
S1C : survivor1区的总容量
S0U : survivor0区已使用的容量
S1U : survivor1区已使用的容量
EC : Eden区的总容量
EU : Eden区已使用的容量
OC : Old区的总容量
OU : Old区已使用的容量
MC:方法区的总容量
MU:方法区已使用的容量
CCSC:压缩类空间的总容量
CCSU:压缩类空间已使用的容量
YGC : 从应用程序启动到采样时young gc的次数
YGCT : 从应用程序启动到采样时young gc消耗时间(秒)
FGC : 从应用程序启动到采样时full gc的次数
FGCT : 从应用程序启动到采样时的full gc的消耗时间(秒)
GCT : 从应用程序启动到采样时gc的总时间
-gccapacity:显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间。
NGCMN : 新生代占用的最小空间
NGCMX : 新生代占用的最大空间
OGCMN : 老年代占用的最小空间
OGCMX : 老年代占用的最大空间
OGC:当前年老代的容量
OC:当前年老代的总容量
MCMN:元空间占用的最小空间
MCCMX:元空间占用的最大空间
-gcutil:显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。
-gccause:与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。
LGCC:上一次垃圾收集发生的原因
GCC:本次垃圾收集发生的原因
-gcnew:显示新生代GC状况
TT:晋升阈值(Tenuring threshold)
MTT:最大晋升阈值(Tenuring threshold)
DSS:期望servivor(幸存区)大小
EC:eden空间总容量
EU:eden空间已使用容量
-gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间
NGCMN : 新生代占用的最小空间
NGCMX : 新生代占用的最大空间
S0CMX: S0最大的空间
S0C:S0的当前容量
ECMX:最大eden空间
EC:eden的当前容量
-geold:显示老年代GC状况
-gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间
-gcpermcapacity:显示永久代使用到的最大、最小空间。
JIT相关的
:
-compiler:显示JIT编译器编译过的方法、耗时等信息
Compiled : 编译数量
Failed : 编译失败数量
Invalid : 无效数量
Time : 编译耗时
FailedType : 失败类型
FailedMethod : 失败方法的全限定名
-printcompilation:输出已经被JIT编译的方法
Compiled:被执行的编译任务的数量
Size:方法字节码的字节数
Type:编译类型
Method:编译方法的类名和方法名。类名使用”/” 代替 “.” 作为空间分隔符. 方法名是给出类的方法名. 格式是一致于HotSpot - XX:+PrintComplation 选项
我们对以上参数做一下说明:
jinfo:实时查看和修改JVM配置参数
jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。
在很多情况卡,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。
在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。
但有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值。
基本使用语法为:jinfo [options] pid
jinfo -sysprops
输出系统属性
也可以在代码中通过System.getProperties()取得的参数
jinfo -flags
输出全部的参数
- 一部分是我们自己设置的
- 另外一部分是系统自动优化设置的参数信息
Non-default表示非默认的,
Command line表示通过命令行赋值的
jinfo -flag
查看某个java进程的具体参数信息
+
启用-
未启用
输出-XX:+UseParallelGC表示使用的是ParallelGC
jinfo 修改参数操作
info并非所有的参数都支持动态修改,它的修改能力是极其有限的,参数只有被标记为manageable的flag可以被实时修改,修改以后立即生效
查看被标记为manageable的参数
- java -XX:+PrintFlagsFinal -version | grep manageable(linux指令)
- java -XX:+PrintFlagsInitial | findstr manageable(window命令)
例如我们修改 PrintGCDetails的值,
首先看一下 PrintGCDetails的原先的值,
使用指令jinfo -flag PrintGCDetails 进程id
说明没有使用PrintGCDetails。
jinfo -flag +PrintGCDetails 进程id进行修改
> 说明已使用PrintGCDetails。
修改一个非boolean类型的,
例如MaxHeapFreeRatio,
使用jinfo -flag MaxHeapFreeRatio 进程id 看一下原来的值为100
使用命令 jinfo -flag MaxHeapFreeRatio=90 进程id 进行修改再进行查看发现已经修改成功。
有个问题需要说明的是如果当前进程结束,再重新开启,那么我们之前修改的数据也会失效
查看所有JVM参数启动的初始值
java -XX:+PrintFlagsInitial PID
查看所有JVM参数的最终值
java -XX:+PrintFlagsFinal
值前面添加冒号:的是修改之后的值,
没有添加的都是没有发生改变的初始值
查看那些已经被用户或者JVM设置过的详细的XX参数的名称和值
java -XX:+PrintCommandLineFlags PID
jmap:导出内存映像文件&内存使用情况
基本情况
jmap(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等
基本用法
[option]
使用1:导出内存映像文件
-
内存的映像文件
一般来说,使用jmap指令生成dump文件的操作算得上是最常用的jmap命令之一,将堆中所有存活对象导出至一个文件之中。Heap Dump又叫做堆存储文件,至一个Java进程在某个时间点的内存快照。Heap Dump在触发内存快照的时候会保存此刻的信息如下:
说明:
(1)通常在写Heap Dump文件前会触发一次Full GC,所以heap dump文件里保存的都是Full GC后留下的对象信息。
(2)由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成的dump文件则需要耗费更长的时间来完成。注意
: -
对于以上说明中的第1点是自动方式才会这样做,而手动不会在Full GC之后生成Dump
-
使用手动方式生成dump文件,一般指令执行之后就会生成,不用等到快出现OOM的时候
-
使用自动方式生成dump文件,当出现OOM之前先生成dump文件
-
如果使用手动方式,一般使用第2种,毕竟生成堆中存活对象的dump文件是比较小的,便于传输和分析
/**
* -Xms60m -Xmx60m -XX:SurvivorRatio=8
*
* @author zhangjianbin
* @date 2021年07月21日22:27
*/
public class GcTest {
public static void main(String[] args) {
ArrayList<byte[]> arrayList = new ArrayList();
for (int i = 0; i < 1000; i++) {
byte[] arr = new byte[1024 * 100];//100KB
arrayList.add(arr);
try {
Thread.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
分为两种导出的方式
- 手动的方式
jmap -dump:format=b,file=<filename.hprof> <pid>
jmap -dump:live,format=b,file=<filename.hprof> <pid>
- 自动的方式
当程序发生OOM退出系统时,一些瞬时信息都随着程序的终止而消失,而OOM问题往往比较困难或者耗时。此时若能在OOM时,自动导出dump文件就显得非常迫切。这里介绍一种比较常用的取得堆快照文件的方法,即:# 当程序发生OOM时,导出应用程序的当前堆快照 -XX:+HeapDumpOnOutOfMemoryError # 可以指定堆快照的保存位置 -XX:HeapDumpPath=<filename.hprof>
## 例如 jvm 参数: -Xms60m -Xmx60m -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\heap\oom.hprof
dump不加文件名时
使用2:显示堆内存相关信息
# 获取某一时刻整个堆空间的统计信息,
# 包括GC的使用、堆配置信息,以及内存的使用信息等
jmap -heap pid
E:\java-project\jvm-video-shk>jmap -heap 12532
Attaching to process ID 12532, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.101-b13
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 62914560 (60.0MB)
NewSize = 20971520 (20.0MB)
MaxNewSize = 20971520 (20.0MB)
OldSize = 41943040 (40.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 16777216 (16.0MB)
used = 6084808 (5.802925109863281MB)
free = 10692408 (10.197074890136719MB)
36.26828193664551% used
From Space:
capacity = 2097152 (2.0MB)
used = 0 (0.0MB)
free = 2097152 (2.0MB)
0.0% used
To Space:
capacity = 2097152 (2.0MB)
used = 0 (0.0MB)
free = 2097152 (2.0MB)
0.0% used
PS Old Generation
capacity = 41943040 (40.0MB)
used = 31191912 (29.746925354003906MB)
free = 10751128 (10.253074645996094MB)
74.36731338500977% used
3123 interned Strings occupying 256536 bytes.
# 输出堆中对象的同级信息,包括类、实例数量和合计容量,
# 也是这一时刻的内存中的对象信息
jmap -histo pid
使用3:其他作用
# 查看系统的ClassLoader信息
jmap -permstat pid
# 查看堆积在finalizer队列中的对象
jmap -finalizerinfo
小节
由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程都停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。
举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live选项将无法探知到这些对象。
另外,如果某个线程长时间无法跑到安全点,jmap将一直等下去。与前面将的jstat则不同,垃圾回收器会主动将jstat所需要的数据保存至固定位置之中,而jstat只需直接读取即可。
jstack:打印JVM中线程快照
基本情况
- jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
- 生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况
- jstack 官网
- 在thread dump中,要留意下面几种状态
死锁,Deadlock(重点关注)
等待资源,Waiting on condition(重点关注)
等待获取监视器,Waiting on monitor entry(重点关注)
阻塞,Blocked(重点关注)
- 执行中,Runnable
- 暂停,Suspended
- 对象等待中,Object.wait() 或 TIMED_WAITING
- 停止, Parked
基本语法
jstack option pid
- jstack远程管理的话,需要在远程程序的启动参数中增加:
-Djava.rmi.server.hostname=...... -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
演示1
/**
* 演示线程的死锁问题
*/
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
演示2
/**
* @author zhangjianbin
* @date 2021年07月24日16:38
*/
public class ThreadSleepTest {
public static void main(String[] args) {
System.out.println("hello-1");
try {
Thread.sleep(1000 * 60 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello-2");
}
}
演示3
/**
*
* @author zhangjianbin
* @date 2021年07月25日9:22
*/
public class ThreadSyncTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
static class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
if (number <= 100) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
} else {
break;
}
}
}
}
}
}
Java层面追踪当前进程中的所有的线程*
public class AllStackTrace {
public static void main(String[] args) {
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();
Set<Map.Entry<Thread, StackTraceElement[]>> entries = all.entrySet();
for (Map.Entry<Thread, StackTraceElement[]> en : entries) {
Thread t = en.getKey();
StackTraceElement[] v = en.getValue();
System.out.println("【Thread name is :" + t.getName() + "】");
for (StackTraceElement s : v) {
System.out.println("\t" + s.toString());
}
}
}
}
jcmd:多功能的命令行
基本情况
- 在JDK1.7以后,新增了一个命令行工具jcmd
- 它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆,内存使用,查看Java进程、导出线程信息、执行GC、JVM运行时间等。
- jcmd 命令官网
- jcmd拥有jmap的大部分功能,并且在Oracle的官方网站上页推荐使用jcmd命令代替jmap命令
# 使用如下命令可以替换jps,即列出所有的JVM进程,加不加-l运行结果一样
jcmd -l
# 针对指定的进程,列出支持的所有命令
jcmd pid help
# 显示指定进程的指令命令的数据
jcmd pid 具体命令
根据以上命令来替换之前的那些操作:
- Thread.print 可以替换 jstack指令
- GC.class_histogram 可以替换 jmap中的-histo操作
- GC.heap_dump 可以替换 jmap中的-dump操作
- GC.run 可以查看GC的执行情况
- VM.uptime 可以查看程序的总执行时间,可以替换jstat指令中的-t操作
- VM.system_properties 可以替换 jinfo -sysprops 进程id
- VM.flags 可以获取JVM的配置参数信息
jstatd:远程主机信息收集
-
之前的命令值涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd工具。
-
命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序传递到远程计算机