1. jstat查看堆内存使用情况
jstat查看堆内存各部分使用量,以及加载类的数量。
命令格式
jstat [-命令选型] [vmid] [时间间隔/毫秒] [查询次数]
1.1 查看class加载情况
[root@localhost bin]# jps
7811 Jps
7725 Bootstrap
[root@localhost bin]# jstat -class 7725
Loaded Bytes Unloaded Bytes Time
3476 7544.4 0 0.0 9.61
- Loader:加载class数量
- Bytes:所占用空间大小
- Unloaded:未加载class数量
- Bytes:未加载占用空间大小
- Time:时间
1.2 查看编译情况
[root@localhost bin]# jps
7835 Jps
7725 Bootstrap
[root@localhost bin]# jstat -compiler 7725
Compiled Failed Invalid Time FailedType FailedMethod
2141 0 0 6.99 0
- Compiled:编译数量
- Failed:失败数量
- Invalid:不可用数量
- Time:时间
- FailedType: 失败类型
- FailedMethod:失败方法
1.3 垃圾回收统一
[root@localhost bin]# jps
7872 Jps
7725 Bootstrap
[root@localhost bin]# jstat -gc 7725
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
832.0 832.0 7.9 0.0 6784.0 1961.1 16880.0 15823.5 24960.0 24218.6 2688.0 2509.4 24 0.479 1 0.025 0.504
#也可以每2秒查询一次,总共查询3次
[root@localhost bin]# jstat -gc 7725 2000 3
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
832.0 832.0 7.9 0.0 6784.0 2031.3 16880.0 15823.5 24960.0 24218.6 2688.0 2509.4 24 0.479 1 0.025 0.504
832.0 832.0 7.9 0.0 6784.0 2031.3 16880.0 15823.5 24960.0 24218.6 2688.0 2509.4 24 0.479 1 0.025 0.504
832.0 832.0 7.9 0.0 6784.0 2031.3 16880.0 15823.5 24960.0 24218.6 2688.0 2509.4 24 0.479 1 0.025 0.504
- S0C:第一个Survivor区的大小(KB)
- S1C:第二个Survivor区的大小(KB)
- S0U:第一个Survivor区的使用大小(KB)
- S1U:第二个Survivor区的使用大小(KB)
- EC:Eden区的大小(KB)
- EU:Eden区的使用大小(KB)
- OC:Old区大小(KB)
- OU:Old使用大小(KB)
- MC:方法区大小(KB)
- MU:方法区使用大小(KB)
- CCSC:压缩类空间大小(KB)
- CCSU:压缩类空间使用大小(KB)
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
2. jmap的使用及内存溢出分析
2.1 查看内存使用情况
[root@localhost bin]# jps
7910 Jps
7725 Bootstrap
[root@localhost bin]# jmap -heap 7725
Attaching to process ID 7725, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.211-b12
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration: #堆内存配置信息
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 255852544 (244.0MB)
NewSize = 5570560 (5.3125MB)
MaxNewSize = 85262336 (81.3125MB)
OldSize = 11206656 (10.6875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage: #堆内存使用情况
New Generation (Eden + 1 Survivor Space): #新生代
capacity = 7798784 (7.4375MB)
used = 4303832 (4.104454040527344MB)
free = 3494952 (3.3330459594726562MB)
55.185936679359244% used
Eden Space:
capacity = 6946816 (6.625MB)
used = 4295720 (4.096717834472656MB)
free = 2651096 (2.5282821655273438MB)
61.83725033166274% used
From Space:
capacity = 851968 (0.8125MB)
used = 8112 (0.0077362060546875MB)
free = 843856 (0.8047637939453125MB)
0.9521484375% used
To Space:
capacity = 851968 (0.8125MB)
used = 0 (0.0MB)
free = 851968 (0.8125MB)
0.0% used
tenured generation: # 老年代
capacity = 17285120 (16.484375MB)
used = 16203232 (15.452606201171875MB)
free = 1081888 (1.031768798828125MB)
93.74092861374407% used
16599 interned Strings occupying 1478152 bytes.
2.2 查看内存对象数量及大小
- 查看所有对象,包括活跃和非活跃
jmap -histo | more - 查看活跃对象
jmap -histo:live | more
[root@localhost ~]# jps
8293 Jps
7725 Bootstrap
[root@localhost ~]# jmap -histo:live 7725 | more
num #instances #bytes class name
----------------------------------------------
1: 31171 3084800 [C
2: 2998 1019856 [I
3: 30124 722976 java.lang.String
4: 917 601920 [B
5: 14199 454368 java.util.HashMap$Node
6: 3903 448896 java.lang.Class
7: 4550 400400 java.lang.reflect.Method
8: 8322 266304 java.util.concurrent.ConcurrentHashMap$Node
9: 4269 261968 [Ljava.lang.Object;
10: 839 164856 [Ljava.util.HashMap$Node;
11: 91 114320 [Ljava.util.concurrent.ConcurrentHashMap$Node;
12: 6336 101376 java.lang.Object
13: 1466 70368 java.util.HashMap
14: 2422 51360 [Ljava.lang.Class;
15: 87 49264 [Ljava.util.WeakHashMap$Entry;
16: 931 48032 [Ljava.lang.String;
17: 1445 46240 java.util.Hashtable$Entry
18: 929 44592 org.apache.tomcat.util.modeler.AttributeInfo
19: 1068 42720 java.util.LinkedHashMap$Entry
20: 1034 33088 java.lang.ref.WeakReference
21: 41 33040 [S
22: 8 32896 [Ljava.nio.ByteBuffer;
23: 1349 32376 java.util.ArrayList
24: 804 32160 java.lang.ref.SoftReference
25: 433 31176 org.apache.jasper.compiler.Node$TemplateText
26: 614 29472 java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync
27: 390 28080 java.util.logging.Logger
--More--
#对象说明
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
2.3 将内存情况dump到文件中
#用法:
jmap ‐dump:format=b,file=dumpFileName <pid>
#示例
jmap ‐dump:format=b,file=/tmp/dump.dat 6219
[root@localhost java]# jps
7725 Bootstrap
8333 Jps
[root@localhost java]# jmap -dump:format=b,file=dump.dat 7725
Dumping heap to /usr/local/src/java/dump.dat ...
Heap dump file created
[root@localhost java]# ll
总用量 28096
drwxr-xr-x. 9 root root 220 9月 13 17:16 apache-tomcat-8.5.40
-rw-r--r--. 1 root root 9690027 9月 13 17:11 apache-tomcat-8.5.40.tar.gz
-rw-------. 1 root root 19077697 9月 14 15:50 dump.dat
drwxr-xr-x. 7 10 143 245 4月 2 11:51 jdk1.8.0_211
drwxr-xr-x. 2 root root 56 5月 4 15:31 mysql5.7
2.4 通过jhat对dump文件进行分析
#用法:
jhat ‐port <port> <file>
[root@localhost java]# jhat -port 9999 dump.dat
Reading from dump.dat...
Dump file created Sat Sep 14 15:50:59 CST 2019
Snapshot read, resolving...
Resolving 192797 objects...
WARNING: Failed to resolve object id 0xf60b0658 for field pkcs8KeySpecClass (signature L)
WARNING: Failed to resolve object id 0xf60b06c0 for field rsaPrivateCrtKeySpecClass (signature L)
WARNING: Failed to resolve object id 0xf60b0728 for field rsaPrivateKeySpecClass (signature L)
WARNING: Failed to resolve object id 0xf60b0790 for field rsaPublicKeySpecClass (signature L)
WARNING: Failed to resolve object id 0xf5ffb8c8 for field returnType (signature L)
WARNING: Failed to resolve object id 0xf5f0f758 for field returnType (signature L)
打开浏览器访问http://192.168.66.129:9999/
最后有OQL功能
2.5 通过mat工具对dump进行分析
2.5.1 mat介绍
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰
富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析
工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止
了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。
官网地址:https://www.eclipse.org/mat/
2.5.2 下载安装
地址:https://www.eclipse.org/mat/downloads.php
解压完启动
2.5.3 使用
3. 实战,MAT分析内存
public class TestJvmOutOfMemory {
// 实现,向集合中添加100万个字符串,每个字符串由100个UUID组成
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
String str = "";
for (int j = 0; j < 1000; j++) {
str += UUID.randomUUID().toString();
}
list.add(str);
}
System.out.println("ok");
}
}
参数:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
结果如下
将java_pid8920.hprof导入MAT分析
分析90.6%都由Object[]占据,比较可疑。
查看详情
4. jstack的使用
查看正在进行的线程情况,录快照,并且打印出来。
用法:jstack <pid>
[root@localhost java]# jps
8488 Jps
7725 Bootstrap
[root@localhost java]# jstack 7725
2019-09-14 18:06:15
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):
"Attach Listener" #44 daemon prio=9 os_prio=0 tid=0x00007fd168003800 nid=0x207a waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"ajp-nio-8009-AsyncTimeout" #42 daemon prio=5 os_prio=0 tid=0x00007fd19050c000 nid=0x1e61 waiting on condition [0x00007fd16c4be000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.coyote.AbstractProtocol$AsyncTimeout.run(AbstractProtocol.java:1151)
at java.lang.Thread.run(Thread.java:748)
"ajp-nio-8009-Acceptor-0" #41 daemon prio=5 os_prio=0 tid=0x00007fd19050a000 nid=0x1e60 runnable [0x00007fd16c5bf000]
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 <0x00000000f6134140> (a java.lang.Object)
4.1 线程的状态
- 初始态(NEW)
创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。 - 运行态(RUNNABLE),在Java中,运行态包括 就绪态 和 运行态。
- 就绪态
该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运
行。
所有就绪态的线程存放在就绪队列中。 - 运行态
获得CPU执行权,正在执行的线程。
由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条
运行态的线程。
- 阻塞态(BLOCKED)
当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
而在Java中,阻塞态专指请求锁失败时进入的状态。
由一个阻塞队列存放所有阻塞态的线程。
处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执
行。 - 等待态(WAITING)
当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
也有一个等待队列存放所有等待态的线程。
线程处于等待态表示它需要等待其他线程的指示才能继续运行。
进入等待态的线程会释放CPU执行权,并释放资源(如:锁) - 超时等待态(TIMED_WAITING)
当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就
会进入该状态;
它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其
他线程唤醒;
进入该状态后释放CPU执行权 和 占有的资源。
与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。 - 终止态(TERMINATED)
线程执行结束后的状态。
4.2 实战:死锁问题
4.2.1 构造死锁问题
编写代码,启动2个线程,Thread1拿到了obj1锁,准备去拿obj2锁时,obj2已经被
Thread2锁定,所以发送了死锁。
public class TestDeadLock {
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{
@Override
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{
@Override
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 的锁!");
}
}
}
}
}
4.2.2 在linux执行
[root@localhost TestDeadLock]# ll
总用量 4
-rw-r--r--. 1 root root 1517 9月 14 18:35 TestDeadLock.java
[root@localhost TestDeadLock]# javac TestDeadLock.java
[root@localhost TestDeadLock]# ll
总用量 20
-rw-r--r--. 1 root root 184 9月 14 21:35 TestDeadLock$1.class
-rw-r--r--. 1 root root 843 9月 14 21:35 TestDeadLock.class
-rw-r--r--. 1 root root 1517 9月 14 18:35 TestDeadLock.java
-rw-r--r--. 1 root root 1078 9月 14 21:35 TestDeadLock$Thread1.class
-rw-r--r--. 1 root root 1078 9月 14 21:35 TestDeadLock$Thread2.class
[root@localhost TestDeadLock]# java TestDeadLock
Thread1 拿到了 obj1 的锁!
Thread2 拿到了 obj2 的锁!
此时重新打开一个连接
4.2.3 使用jstack分析问题
[root@localhost ~]# jps
8807 Jps
8794 TestDeadLock
7725 Bootstrap
[root@localhost ~]# jstack 8794
2019-09-14 21:37:34
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):
"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007fbd8c001000 nid=0x227b waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007fbdb4009800 nid=0x225b waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #9 prio=5 os_prio=0 tid=0x00007fbdb40cc000 nid=0x2265 waiting for monitor entry [0x00007fbdb81e8000]
java.lang.Thread.State: BLOCKED (on object monitor)
at TestDeadLock$Thread2.run(TestDeadLock.java:46)
- waiting to lock <0x00000000f0c5cc68> (a java.lang.Object)
- locked <0x00000000f0c5cc78> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #8 prio=5 os_prio=0 tid=0x00007fbdb40ca000 nid=0x2264 waiting for monitor entry [0x00007fbdb82e9000]
java.lang.Thread.State: BLOCKED (on object monitor)
at TestDeadLock$Thread1.run(TestDeadLock.java:26)
- waiting to lock <0x00000000f0c5cc78> (a java.lang.Object)
- locked <0x00000000f0c5cc68> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fbdb40b4000 nid=0x2262 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fbdb40b1000 nid=0x2261 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fbdb40af000 nid=0x2260 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fbdb40ad800 nid=0x225f runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fbdb407a800 nid=0x225e in Object.wait() [0x00007fbdb88ef000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f0c08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000f0c08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fbdb4077800 nid=0x225d in Object.wait() [0x00007fbdb89f0000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f0c06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000f0c06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=0 tid=0x00007fbdb406e000 nid=0x225c runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007fbdb40b7000 nid=0x2263 waiting on condition
JNI global references: 5
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fbd980062c8 (object 0x00000000f0c5cc68, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007fbd98004e28 (object 0x00000000f0c5cc78, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at TestDeadLock$Thread2.run(TestDeadLock.java:46)
- waiting to lock <0x00000000f0c5cc68> (a java.lang.Object) # 等待获取锁
- locked <0x00000000f0c5cc78> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at TestDeadLock$Thread1.run(TestDeadLock.java:26)
- waiting to lock <0x00000000f0c5cc78> (a java.lang.Object) # 等待获取锁
- locked <0x00000000f0c5cc68> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
关键看Java stack information for the threads listed above: 发生死锁。
5. VisualVM工具的使用
VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的
对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。
VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的
所有功能。
- 内存信息
- 线程信息
- Dump堆(本地进程)
- Dump线程(本地进程)
- 打开堆Dump。堆Dump可以用jmap来生成。
- 打开线程Dump
- 生成应用快照(包含内存信息、线程信息等等)
- 性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类
对象占用的内存,检查哪些类占用内存多)
5.1 启动
在jdk安装目录下的bin目录下,找到jvisualvm.exe
5.2 查看本地进程
5.3 查看cpu、内存、类和线程信息
5.4 查看线程详情
也可以点击右上角的dump,其实就是执行jstack命令。
5.5 抽样器
5.6 远程监控
需要借助JMX技术实现。
5.6.1 什么是JMX
JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
5.6.2 远程监控tomcat的配置
在tomcat的bin目录下,修改catalina.sh, 添加如下参数:
注意关闭防火墙
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=192.168.66.129"
#这几个参数的意思是:
#‐Dcom.sun.management.jmxremote :允许使用JMX远程管理
#‐Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口
#‐Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用
户都可以连接
#‐Dcom.sun.management.jmxremote.ssl=false :不使用ssl
保存退出,重启tomcat
5.6.3 远程监控