jps 是(java process Status Tool), Java版的ps命令,查看java进程及其相关的信息,如果你想找到一个java进程的pid,那可以用jps命令替代linux中的ps命令了,简单而方便。
命令格式:
jps [options] [hostid]
options参数解释:
hostid : 主机或其他服务器ip
最常用示例:
jps -l 输出jar包路径,类全名
jps -m 输出main参数
jps -v 输出JVM参数
参考代码
/**
* jps -q 显示进程id
* jps -l 输出jar包路径,类全名
* jps -m 输出主类名,及传入main方法的参数
* jps -v 输出主类名,及输出JVM参数
*/
public class JPSTest {
public static void main(String[] args) {
System.out.println("jps 指令");
try {
Thread.sleep(200000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
从图中可以看到,jps命令输出了我们的JPSTest任务,进程id是12652。
其他的参数也可以一一去尝试
jinfo是用来查看JVM参数和动态修改部分JVM参数的命令
命令格式
jinfo [option]
options参数解释:
参考代码
/**
jinfo [option]
options参数解释:
- no options 输出所有的系统属性和参数
- -flag 打印指定名称的参数
- -flag [+|-] 打开或关闭参数
- -flag = 设置参数
- -flags 打印所有参数
- -sysprops 打印系统配置
**/
public class Demo2_jinfo {
public static void main(String[] args) {
System.out.println("jinfo 指令");
try {
Thread.sleep(2000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
首先先用jps命令,找到当前类运行的进程pid,我这里是pid=27980
然后利用该pid,执行jinfo命令
查看JVM参数和系统配置
jinfo 27980
jinfo -flags 27980
jinfo -sysprops 27980
查看打印GC日志参数
jinfo -flag PrintGC 27980
jinfo -flag PrintGCDetails 27980
打开GC日志参数
jinfo -flag +PrintGC 27980
jinfo -flag +PrintGCDetails 27980
关闭GC日志参数
jinfo -flag -PrintGC 27980
jinfo -flag -PrintGCDetails 27980
还可以使用下面的命令查看那些参数可以使用jinfo命令来管理:
常用JVM参数
is translated
in a VM flag named ThreadStackSize”一般设置这个值就可以了。jstat命令是使用频率比较高的命令,主要用来查看JVM运行时的状态信息,包括内存状态、垃圾回收等。
命令格式
jstat [option] VMID [interval] [count]
其中VMID是进程id,interval是打印间隔时间(毫秒),count是打印次数(默认一直打印)
option参数解释
常用示例及打印字段解释
参考代码
public class Demo3_jstat {
public static void main(String[] args) {
System.out.println("jstat 指令");
try {
Thread.sleep(2000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同样需要先利用jps命令找到当前类运行的进程pid,这里是pid=4856
jstat -gcutil 4856 1000 3
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 8.00 0.00 17.35 19.94 0 0.000 0 0.000 0.000
0.00 0.00 8.00 0.00 17.35 19.94 0 0.000 0 0.000 0.000
0.00 0.00 8.00 0.00 17.35 19.94 0 0.000 0 0.000 0.000
上述命令中,4856为pid,每隔1000毫秒打印一次,打印3次。
字段解释:
jstat -gc 4856 1000 3
-gc和-gcutil参数类似,只不过输出字段不是百分比,而是实际的值。
输出
jstat -gc 4856 1000 3
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
15872.0 15872.0 0.0 0.0 98304.0 7864.4 261120.0 0.0 4480.0 777.2 384.0 76.6 0 0.000 0 0.000 0.000
15872.0 15872.0 0.0 0.0 98304.0 7864.4 261120.0 0.0 4480.0 777.2 384.0 76.6 0 0.000 0 0.000 0.000
15872.0 15872.0 0.0 0.0 98304.0 7864.4 261120.0 0.0 4480.0 777.2 384.0 76.6 0 0.000 0 0.000 0.000
字段解释:
jstack是用来查看JVM线程快照的命令,线程快照是当前JVM线程正在执行的方法堆栈集合。使用jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环等。jstack还可以查看程序崩溃时生成的core文件中的stack信息
命令格式
jstack [options]
option参数解释:
cpu占用过高问题
jstack检查死锁问题
public class DeadLock {
private static Object obj1 = new Object();
private static Object obj2 = new Object();
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
private static class Thread1 implements Runnable{
public void run() {
synchronized (obj1){
System.out.println("Thread1 拿到了 obj1 的锁!");
try {
// 停顿2秒的意义在于,让Thread2线程拿到obj2的锁
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("Thread1 拿到了 obj2 的锁!");
}
}
}
}
private static class Thread2 implements Runnable{
public void run() {
synchronized (obj2){
System.out.println("Thread2 拿到了 obj2 的锁!");
try {
// 停顿2秒的意义在于,让Thread1线程拿到obj1的锁
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("Thread2 拿到了 obj1 的锁!");
}
}
}
}
}
上图代码是一个非常明显的死锁演示代码,其中Thread1首先获取对象obj1的锁,然后休眠的时候Thread2获取到对象obj2的锁,而Thread1和Thread2又同时在尝试获取对方持有的锁,符合死锁的条件。
执行指令
jstack -l 28032
打印结果
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00000267fbaa2d48 (object 0x0000000741349460, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00000267fbaa55d8 (object 0x0000000741349470, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.lagou.concurrent.demo.test.DeadLock$Thread2.run(DeadLock.java:39)
- waiting to lock <0x0000000741349460> (a java.lang.Object)
- locked <0x0000000741349470> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.lagou.concurrent.demo.test.DeadLock$Thread1.run(DeadLock.java:22)
- waiting to lock <0x0000000741349470> (a java.lang.Object)
- locked <0x0000000741349460> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
jmap可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及finalizer 队列
命令格式
jmap [option] (连接正在执行的进程)
option参数解释
常用示例
jmap -dump:live,format=b,file=dump.bin 15084
输出:
Dumping heap to /dump.bin ...
Heap dump file created
这个命令是要把java堆中的存活对象信息转储到dump.bin文件
jmap -finalizerinfo 15084
输出:
Attaching to process ID 15084, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.281-b09
Number of objects pending for finalization: 0
输出结果的含义为当前没有在等待执行finalizer方法的对象
jmap -heap 15084
输出堆的详细信息
Attaching to process ID 15084, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.281-b09
using thread-local object allocation.
Parallel GC with 10 thread(s)
Heap Configuration: //堆内存初始化配置
MinHeapFreeRatio = 0 //对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
MaxHeapFreeRatio = 100 //对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
MaxHeapSize = 6385827840 (6090.0MB) //对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize = 133169152 (127.0MB) //对应jvm启动参数-XX:NewSize=设置JVM堆的新生代的默认大小
MaxNewSize = 2128609280 (2030.0MB) //对应jvm启动参数-XX:MaxNewSize=设置JVM堆的新生代的最大大小
OldSize = 267386880 (255.0MB) //对应jvm启动参数-XX:OldSize=:设置JVM堆的老年代的大小
NewRatio = 2 //对应jvm启动参数-XX:NewRatio=:新生代和老生代的大小比率
SurvivorRatio = 8 //对应jvm启动参数-XX:SurvivorRatio=设置新生代中Eden区与Survivor区的大小比值
MetaspaceSize = 21807104 (20.796875MB) // 元数据区大小
CompressedClassSpaceSize = 1073741824 (1024.0MB) //类压缩空间大小
MaxMetaspaceSize = 17592186044415 MB /元数据区最大大小
G1HeapRegionSize = 0 (0.0MB) //G1垃圾收集器每个Region大小
Heap Usage: //堆内存使用情况
PS Young Generation
Eden Space: //Eden区内存分布
capacity = 100663296 (96.0MB) //Eden区总容量
used = 0 (0.0MB) //Eden区已使用
free = 100663296 (96.0MB) //Eden区剩余容量
0.0% used //Eden区使用比率
From Space: //其中一个Survivor区的内存分布
capacity = 16252928 (15.5MB)
used = 0 (0.0MB)
free = 16252928 (15.5MB)
0.0% used
To Space: //另一个Survivor区的内存分布
capacity = 16252928 (15.5MB)
used = 0 (0.0MB)
free = 16252928 (15.5MB)
0.0% used
PS Old Generation
capacity = 101711872 (97.0MB) //老年代容量
used = 981064 (0.9356155395507812MB) //老年代已使用
free = 100730808 (96.06438446044922MB) //老年代空闲
0.9645521026296714% used //老年代使用比率
3156 interned Strings occupying 259152 bytes.
jmap -histo:live 15084 | more
输出存活对象统计信息
num #instances #bytes class name
----------------------------------------------
1: 4563 425096 [C
2: 415 134688 [B
3: 4414 105936 java.lang.String
4: 706 80888 java.lang.Class
5: 632 41512 [Ljava.lang.Object;
6: 791 31640 java.util.TreeMap$Entry
7: 628 25120 java.util.LinkedHashMap$Entry
8: 426 19072 [Ljava.lang.String;
9: 372 11904 java.util.HashMap$Node
10: 24 8640 [Ljava.util.HashMap$Node;
....
jhat是用来分析jmap生成dump文件的命令,jhat内置了应用服务器,可以通过网页查看dump文件分析结果,jhat一般是用在离线分析上。
命令格式
jhat [option] [dumpfile]
option参数解释 :
常用实例
jhat dump.bin
Jconsole(Java Monitoring and Management Console)是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监测工具。jconsole使用jvm的扩展机制获取并展示虚拟机中运行的应用程序的性能和资源消耗等信息。
直接在jdk/bin目录下点击jconsole.exe即可启动
代码准备
public class JConsoleDemo {
static class OOMObject {
// 每个字节数组占8K内存
public byte[] placeholder = new byte[8 * 1024];
}
public static void fillHeap(int num) throws InterruptedException {
List<OOMObject> list = new ArrayList<OOMObject>();
for (int i = 0; i < num; i++) {
Thread.sleep(50);
list.add(new OOMObject());
}
System.gc();
}
public static void main(String[] args) throws Exception {
fillHeap(1000);
//System.gc();
Thread.sleep(10000);
}
}
编译运行JConsoleDemo类, 运行时设置的虚拟机参数为 -Xms100m -Xmx100m -XX:+UseSerialGC ,在%JAVA_HOME%\bin目录下, 启动jconsole.exe , 将自动搜索出本机运行的所有虚拟机进程, 这里我们选择JConsoleDemo对应的进程24036。
启动后主界面如下:
“概览”页签显示的是整个虚拟机主要运行数据的概览,其中包括“堆内存使用情况”、“线程”、“类”、“CPU使用情况”4种信息的曲线图。这些曲线图是后面“内存” 、“线程”、 ‘类”页签的信息汇总,具体内容将在后面介绍。
在"内存"页签, 查看堆内存Eden区的运行趋势如下:
从图中详细信息可以看出, Eden区的内存大小为27.328KB, 所以折线图中显示每次到27Mb左右时系统就会进行一次GC。当1000次循环结束后, 执行System.gc(), 柱状图中显示Eden区和Survivor区基本被清空, 但老年代的对应柱状图仍保持峰值状态, 这是因为System.gc()是在fillHeap()方法内执行, 所以list对象在System.gc()执行时仍然是存活的( 处于作用域之内、被引用)。 如果将System.gc()移动到fillHeap()方法外执行, 如下图所示, 则会回收包括老年代的所有内存。
查看CPU使用率及活锁阻塞线程
代码准备
public class Demo7_JConsole02 {
/**
* 线程死循环演示
*/
public static void createBusyThread() {
Thread thread = new Thread(new Runnable() {
// 无限循环
public void run() {
while (true);
}
}, "testBusyThread");
System.out.println("启动testBusyThread 线程完毕..");
thread.start();
}
/
**
* 线程锁等待演示
*/
public static void createLockThread(final Object lock) {
Thread thread = new Thread(new Runnable() {
// 创建一个活锁线程
public void run() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "testLockThread");
thread.start();
System.out.println("启动testLockThread 线程完毕..");
}
public static void main(String[] args) throws Exception {
System.out.println("main 线程..");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("redLine阻塞");
br.readLine();
createBusyThread();
System.out.println("redLine阻塞");
br.readLine();
Object obj = new Object();
createLockThread(obj);
System.out.println("main 线程结束..");
}
}
启动代码,在控制台执行JConsole命令观察
首先执行BusyThread线程
可以看到,在执行BusyThread线程时,右下角的CPU占用率从一开始比较低的值一下升高,再趋于平稳。原因就是我们新启动了一个线程,占用了一定的CPU,同时由于该线程无限循环,所以CPU占用率再提升到一定值后会趋于平稳,而不是执行结束后回落。
接下来,再执行LockThread线程
在”线程“的菜单栏中可以看到,testLockThread线程的状态被标记为WAITING,而且根据堆栈追踪可知,线程WAITING在代码的第27行。
通过上述分析可以看出,JConsole工具为我们提供了丰富的观察JVM某一进程执行情况的各项指标,帮助我们追踪代码运行性能和问题回溯。
VisualVM 是一个工具,它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于Java 技术的应用程序(Java 应用程序)的详细信息。VisualVM 对 Java Development Kit (JDK) 工具所检索的 JVM 软件相关数据进行组织,并通过一种使您可以快速查看有关多个 Java 应用程序的数据的方式提供该信息。您可以查看本地应用程序以及远程主机上运行的应用程序的相关数据。此外,还可以捕获有关 JVM 软件实例的数据,并将该数据保存到本地系统,以供后期查看或与其他用户共享。
VisualVM基于NetBeans平台开发, 因此它一开始就具备了插件扩展的特性, 通过插件支持, VisualVM可以做许多事情。例如:
在%JAVA_HOME%\bin目录下, 启动jvisualvm.exe进入主界面, 点击"工具"→"插件"→"可用插件"选项, 选择所需的插件安装。
安装好插件后, 选择一个正在运行的java程序就可以查看程序监控的主界面了
测试代码:
public class Demo8_JVisualVM {
public static void main(String[] args) throws IOException, InterruptedException {
System.in.read(); // 阻塞,只要输入回车,就会向下执行代码
fun();
System.in.read();
}
private static void fun() throws InterruptedException {
List<Capacity> list = new ArrayList<>();
for(int i = 0; i < 10000; i++){
Thread.sleep(400);
list.add(new Capacity());
}
}
}
class Capacity{
private byte[] big = new byte[8 * 1024 * 1024]; // 8M
}
执行测试程序,在Terminal中输入jvisualVM,观察jvisualVM工具对进程运行各项指标的监控展示。
在启动页面可以看到我们执行的进程:Demo8_JVisualVM,pid=18684
双击点开对应进程,可以看到该进程执行情况的相应概述:
接下来,在控制台中输入回车,即会执行fun
方法,我们通过Visual GC标签栏,来观察程序运行时的GC过程。(Visual GC标签栏可以通过导航栏的 工具->插件->可用插件 里下载):
从上图中我们可以看到,JVM各区域的内存使用情况。在fun
方法中,每隔400ms就会往队列中添加一个8k大小的数组。可以看到Eden Space区的图形成锯齿状,原因是每次到达峰值时,就会进行一次Young GC。由于fun
方法中list
引用一直持有这些数据,因此这些数据不会被GC回收,多次未被回收会就被放入老年代中。这也是老年代内存空间持续增加的原因。最终,老年代一直增加达到阈值就会报错Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
其他标签栏中的性能监控展示,跟JConsole里的类似,这里就不重复介绍了,有兴趣的同学可以自己动手试一试,利用一些测试代码,然后通过JConsole或JvisualVM可视化工具,来观察程序运行过程中各项指标的情况,以及GC过程各区域的变化情况,也有利于自己对GC过程的深入理解。