在企业级 Java 应用开发中,Java 程序运行过程中会经常遇到内存不足、内存泄露、线程死锁、CPU 高占用等问题。
部分问题在日常开发中可能会被忽视或被别变通的方法绕开(比如重启服务或者调大内存),而不被深究问题的根源,如何理解并解决这些问题需要我们学会使用一些 JVM 性能调优监控工具。
本文将简单介绍常用的 JVM 性能调优监控工具:jps、jinfo、jmap、jstat 和 jstack。
1. jps
jps(Java Virtual Machine Process Status Tool)命令用于输出 JVM 中运行的进程状态信息。命令格式如下:jps [-q] [-mlvV] [<hostid>]
jps [-help]
参数说明:
-q:不输出应用程序主类的名称、JAR文件名和传递给main()方法的参数,只显示 pid;
-mlvV:可以指定这些参数的任意组合,各自的作用如下:
-m:输出传递给 main 方法的参数,如果是内嵌的 JVM 则输出为 null;
-l:输出应用程序主类的完整包名,或者是应用程序 JAR 文件的完整路径;
-v:输出传给 JVM 的参数;
-V:输出通过标记的文件传递给 JVM 的参数(.hotspotrc 文件,或者是通过参数 -XX:Flags= 指定的文件);
hostid:指定的远程主机,可以是 IP 地址和域名, 也可以指定具体协议、端口。如果不指定,则显示本机的 Java 虚拟机的进程信息;
-help:显示 jps 命令的帮助信息。
示例:
以 Windows 10 的 Java 环境为例,在 D:\demo 目录下创建 App.java 文件,内容如下:
public class App { public static void main(String[] args) { try { int i = 0; while (i++ < 120) { System.out.println("i = " + i); Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println("interrupted"); } } }
在命令行控制台下,命令行编译运行:
D:\demo>javac App.java D:\demo>java App -a i = 1 i = 2 i = 3 ...
App 程序运行 120 秒,在 App 结束前,打开一个新的命令行控制台,运行如下命令:
C:\Users\admin>jps 17528 Jps 17388 App C:\Users\admin>jps -m -l 17388 App -a 19324 sun.tools.jps.Jps -m -l
注:jps 运行了两次,所以两次的 pid 不一样,App 是同一个进程,所以 pid 都是 17388。
2. jinfo
jinfo (Java Virtual Machine Configuration Information) 命令用于查看 Java 进程运行的 JVM 参数,命令格式如下:
jinfo [option] <pid>
jinfo [option] <executable <core>
jinfo [option] [server_id@]<remote server IP or hostname>
参数 option:
-flag <name> 输出指定 VM 参数的值
-flag [+|-]<name> 开启/关闭指定 VM 参数
-flag <name>=<value> 设置指定 VM 参数的值
-flags 输出全部 VM 参数的值
-sysprops 输出 Java 系统属性
<no option> 输出以上全部属性
-h | -help 输出帮助信息
示例:
以上文的 App 程序为例,运行 App:
D:\demo>java App -a i = 1 i = 2 i = 3 ...
App 程序运行 120 秒,在 App 运行结束前,打开一个新的命令行控制台,运行如下命令:
C:\Users\admin>jps 1944 App 488 Jps C:\Users\admin>jinfo -flags 1944 Attaching to process ID 1944, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.121-b13 Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4242538496 -XX:MaxNewSize=1414004736 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC Command line: C:\Users\admin>jinfo -flag InitialHeapSize 1944 -XX:InitialHeapSize=266338304 C:\Users\admin>jinfo -sysprops 1944 Attaching to process ID 1944, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.121-b13 java.runtime.name = Java(TM) SE Runtime Environment java.vm.version = 25.121-b13 sun.boot.library.path = C:\Program Files\Java\jre1.8.0_121\bin java.vendor.url = http://java.oracle.com/ java.vm.vendor = Oracle Corporation path.separator = ; file.encoding.pkg = sun.io java.vm.name = Java HotSpot(TM) 64-Bit Server VM sun.os.patch.level = sun.java.launcher = SUN_STANDARD user.script = user.country = CN user.dir = D:\demo java.vm.specification.name = Java Virtual Machine Specification java.runtime.version = 1.8.0_121-b13 java.awt.graphicsenv = sun.awt.Win32GraphicsEnvironment os.arch = amd64 java.endorsed.dirs = C:\Program Files\Java\jre1.8.0_121\lib\endorsed line.separator = ... # 查看是否有 PrintGC 参数 C:\Users\admin>jinfo -flag PrintGC 1944 -XX:-PrintGC # 开启 PrintGC 参数 C:\Users\admin>jinfo -flag +PrintGC 1944 C:\Users\admin>jinfo -flag PrintGC 1944 -XX:+PrintGC # 关闭 PrintGC 参数 C:\Users\admin>jinfo -flag -PrintGC 1944 C:\Users\admin>jinfo -flag PrintGC 1944 -XX:-PrintGC
3. jmap
jmap(Java Virtual Machine Memory Map)命令用于生成 Java 虚拟机(JVM)的堆转储快照 dump 文件。此外,jmap 命令还可以查看 finalize 执行队列、Java 堆和方法区的详细信息,比如空间使用率、当前使用的什么垃圾回收器、分代情况等等。命令格式如下:
jmap [option] <pid>
参数 option:
-heap:显示 Java 堆的信息;
-histo[:live]:显示 Java 堆中对象的统计信息,包括:对象数量、占用内存大小(单位:字节)和类的完全限定名。
live 子参数可选,如果指定,则只计算活动的对象;
-clstats:显示 Java 堆中元空间的类加载器的统计信息;
-finalizerinfo:显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象;
-dump:[live,]format=b,file=:生成 Java 虚拟机的堆转储快照 dump 文件。具体说明如下:
live 子参数是可选,如果指定,则只转储堆中的活动对象;
format=b 表示以 hprof 二进制格式转储 Java 堆的内存。
file=<filename> 用于指定快照 dump 文件的文件名。
-F:强制模式。如果指定的 pid 没有响应,可以配合 -dump 或 -histo 一起使用。此模式下,不支持 live 参数。
-h 和 -help:显示 jinfo 命令的帮助信息。
<no option>:jinfo 命令会显示 Java 虚拟机进程的内存映像信息。
示例:
以上文的 App 程序为例,运行 App:
D:\demo>java App -a i = 1 i = 2 i = 3 ...
App 程序运行 120 秒,在 App 运行结束前,打开一个新的命令行控制台,运行如下命令:
C:\Users\admin>jps 18996 Jps 9128 App C:\Users\admin>jmap -heap 9128 Attaching to process ID 9128, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.121-b13 using thread-local object allocation. Parallel GC with 8 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 4242538496 (4046.0MB) NewSize = 88604672 (84.5MB) MaxNewSize = 1414004736 (1348.5MB) OldSize = 177733632 (169.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: PS Young Generation ... 1601 interned Strings occupying 148128 bytes. C:\Users\admin>jmap -histo:live 9128 num #instances #bytes class name ---------------------------------------------- 1: 2273 394784 [C 2: 487 55624 java.lang.Class 3: 2003 48072 java.lang.String 4: 837 33480 java.util.TreeMap$Entry 5: 515 29560 [Ljava.lang.Object; ... Total 7813 655544 C:\Users\admin>jmap -dump:live,format=b,file=d:\heap_test.bin 9128 Dumping heap to D:\heap_test.bin ... Heap dump file created
4. jstat
jstat(Java Virtual Machine Statistics Monitoring Tool)命令用于查看 Java 虚拟机(JVM)的运行状态信息,它可以显示 Java 虚拟机中的类加载、内存、垃圾收集、即时编译等运行状态信息。命令格式如下:
jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]] <pid>
参数说明:
-help:显示帮助信息。
-options:显示输出选项参数的列表。
-<option>:输出选项,指定显示某一种 Java 虚拟机信息。默认以输出选项决定 jstat 命令显示的内容和格式,选项如下:
-class:显示类加载、卸载数量、总空间和装载耗时的统计信息。
-compiler:显示即时编译的方法、耗时等信息。
-gc:显示堆各个区域内存使用和垃圾回收的统计信息。
-gccapacity:显示堆各个区域的容量及其对应的空间的统计信息。
-gcutil:显示有关垃圾收集统计信息的摘要。
-gccause:显示关于垃圾收集统计信息的摘要(与-gcutil相同),以及最近和当前垃圾回收的原因。
-gcnew:显示新生代的垃圾回收统计信息。
-gcnewcapacity:显示新生代的大小及其对应的空间的统计信息。
-gcold: 显示老年代和元空间的垃圾回收统计信息。
-gcoldcapacity:显示老年代的大小统计信息。
-gcmetacapacity:显示元空间的大小的统计信息。
-printcompilation:显示即时编译方法的统计信息。
-t:把时间戳列显示为输出的第一列。这个时间戳是从 Java 虚拟机的开始运行到现在的秒数。
-h n:每显示 n 行显示一次表头,其中 n 为正整数。默认值为 0,即仅在第一行数据显示一次表头。
vmid:虚拟机唯一 ID(LVMID,Local Virtual Machine Identifier),如果查看本机就是 Java 进程的进程 ID。
interval:显示信息的时间间隔,单位默认毫秒。也可以指定秒为单位,比如:1s。如果指定了该参数,jstat 命令将每个这段时间显示一次统计信息。
count:显示数据的次数,默认值是无穷大,这将导致 jstat 命令一直显示统计信息,直到目标JVM终止或 jstat 命令终止。
示例:
以上文的 App 程序为例,运行 App:
D:\demo>java App -a i = 1 i = 2 i = 3 ...
App 程序运行 120 秒,在 App 运行结束前,打开一个新的命令行控制台,运行如下命令:
C:\Users\admin>jps 22436 App 17944 Jps C:\Users\admin>jstat -class 22436 Loaded Bytes Unloaded Bytes Time 420 856.9 0 0.0 0.03 C:\Users\admin>jstat -compiler -t 22436 Timestamp Compiled Failed Invalid Time FailedType FailedMethod 63.4 35 0 0 0.01 0 C:\Users\admin>jstat -gcnew 22436 S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 10752.0 10752.0 0.0 0.0 15 15 0.0 65024.0 2601.0 0 0.000
5. jstack
jstack(Java Virtual Machine Stack Trace)用于生成 Java 虚拟机当前时刻的线程快照信息。线程快照一般被称为 thread dump 或者 javacore 文件,是当前 Java 虚拟机中每个线程正在执行的 Java 线程、虚拟机内部线程和可选的本地方法堆栈帧的集合。
对于每个方法栈帧,将会显示完整的类名、方法名、字节码索引 (bytecode index,BCI) 和行号。生成的线程快照可以用于定位线程出现长时间停顿的原因,比如:线程间死锁、死循环、请求外部资源被长时间挂起等信息。
命令格式如下:
jstack [-l] <pid>
jstack -F [-m] [-l] <pid>
jstack [-m] [-l] <executable> <core>
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
参数说明:
-F:当正常请求不被响应时 (挂起),强制输出线程快照信息;
-m:显示 Java 方法栈帧和本地方法栈帧(混合模式),本地方法栈帧是 C 或 C++ 编写的虚拟机代码或 JNI/native 代码;
-l:显示关于锁的附加信息,比如属于 java.util.concurrent 的 ownable synchronizers 列表;
-h 和 -help:显示 jstack 命令的帮助信息。
示例:
以上文的 App 程序为例,运行 App:
D:\demo>java App -a i = 1 i = 2 i = 3 ...
App 程序运行 120 秒,在 App 运行结束前,打开一个新的命令行控制台,运行如下命令:
C:\Users\admin>jps 13844 Jps 6332 App C:\Users\admin>jstack -l 6332 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode): ... "main" #1 prio=5 os_prio=0 tid=0x00000000028b0800 nid=0x46c4 waiting on condition [0x00000000027cf000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at App.main(App.java:7) Locked ownable synchronizers: - None "VM Thread" os_prio=2 tid=0x000000001c2f9000 nid=0x5770 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002b16000 nid=0x4b0 runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002b17800 nid=0x556c runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002b19000 nid=0x3cf8 runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002b1b800 nid=0x5304 runnable "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002b1d000 nid=0x4de4 runnable "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002b1e000 nid=0x55c8 runnable "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002b22000 nid=0x4f80 runnable "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002b23800 nid=0x1c1c runnable "VM Periodic Task Thread" os_prio=2 tid=0x000000001e34c000 nid=0x2340 waiting on condition JNI global references: 6
在 D:\demo 目录下创建 App2.java 文件,模拟 2 个线程死锁的场景,代码内容如下:
public class App2 { public static void main( String[] args ) { System.out.println("App2 running ..."); Thread t1 = new Thread(new DeadLock(true), "Thread DeadLock 1"); Thread t2 = new Thread(new DeadLock(false), "Thread DealLock 2"); t1.start(); t2.start(); } } class DeadLock implements Runnable{ private static Object obj1 = new Object(); private static Object obj2 = new Object(); private boolean flag; public DeadLock(boolean flag){ this.flag = flag; } @Override public void run(){ String name = Thread.currentThread().getName(); System.out.println(name + ":running ..."); /* * 同时开启线程1和线程2: * 1. 如果让线程1执行 "代码1"(flag==true),让线程2执行 "代码2" (flag==false), 会死锁; * 2. 如果让线程1执行 "代码2"(flag==false),让线程2执行 "代码1" (flag==true), 会死锁; * 3. 如果让线程1执行 "代码1"(flag==true),让线程2执行 "代码1" (flag==true), 不会死锁; * 4. 如果让线程1执行 "代码2"(flag==false),让线程2执行 "代码2" (flag==false), 不会死锁; */ if (flag) { // 代码1 synchronized(obj1){ System.out.println(name + ": lock obj1"); System.out.println(name + ": try to lock obj2 ..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj2){ // 死锁时执行不到这里 System.out.println(name +": after 1 second, lock obj2"); } } } else { // 代码2 synchronized(obj2){ System.out.println(name + ": lock obj2"); System.out.println(name + ": try to lock obj1 ..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj1){ // 死锁时执行不到这里 System.out.println(name +": after 1 second, lock obj1"); } } } } }
在命令行控制台下,命令行编译运行:
D:\demo>javac -encoding UTF-8 App2.java D:\demo>java App2 App2 running ... Thread DeadLock 1:running ... Thread DealLock 2:running ... Thread DealLock 2: lock obj2 Thread DealLock 2: try to lock obj1 ... Thread DeadLock 1: lock obj1 Thread DeadLock 1: try to lock obj2 ...
打开一个新的命令行控制台,运行如下命令:
C:\Users\admin>jps 6512 App2 14440 Jps C:\Users\admin>jstack -l 6512 ... JNI global references: 6 Found one Java-level deadlock: ============================= "Thread DealLock 2": waiting to lock monitor 0x0000000002c3afc8 (object 0x000000076bc5f4e0, a java.lang.Object), which is held by "Thread DeadLock 1" "Thread DeadLock 1": waiting to lock monitor 0x0000000002c3da68 (object 0x000000076bc5f4f0, a java.lang.Object), which is held by "Thread DealLock 2" Java stack information for the threads listed above: =================================================== "Thread DealLock 2": at DeadLock.run(App2.java:60) - waiting to lock <0x000000076bc5f4e0> (a java.lang.Object) - locked <0x000000076bc5f4f0> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) "Thread DeadLock 1": at DeadLock.run(App2.java:45) - waiting to lock <0x000000076bc5f4f0> (a java.lang.Object) - locked <0x000000076bc5f4e0> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) Found 1 deadlock. C:\Users\admin>jstack -m -l 6512 Attaching to process ID 6512, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.121-b13 Deadlock Detection: Found one Java-level deadlock: ============================= "Thread DeadLock 1": waiting to lock Monitor@0x0000000002c3da68 (Object@0x000000076bc5f4f0, a java/lang/Object), which is held by "Thread DealLock 2" "Thread DealLock 2": waiting to lock Monitor@0x0000000002c3afc8 (Object@0x000000076bc5f4e0, a java/lang/Object), which is held by "Thread DeadLock 1" Found a total of 1 deadlock. ...
标签:...,java,Users,Thread,虚拟机,JVM,Java,App From: https://www.cnblogs.com/tkuang/p/17138268.html