Jmap命令
jmap -histo
jmap -histo 此命令可以用来查看内存信息,实例个数以及占用内存大小
# jmap -histo 进程ID > 输出到某个文件
jmap -histo 14660 > ./log.txt
查看文件内存
num #instances #bytes class name
----------------------------------------------
1: 41571 517973816 [I
2: 405925 43569608 [C
3: 75427 29113784 [B
4: 321134 7707216 java.lang.String
5: 85077 7486776 java.lang.reflect.Method
6: 113381 5444792 [Ljava.lang.Object;
7: 113029 3616928 java.util.concurrent.ConcurrentHashMap$Node
8: 112757 3608224 java.util.HashMap$Node
9: 28520 2999152 [Ljava.util.HashMap$Node;
10: 19450 2180480 java.lang.Class
11: 42386 1695440 java.util.LinkedHashMap$Entry
12: 72733 1652456 [Ljava.lang.Class;
13: 33601 1612848 java.util.HashMap
14: 62548 1501152 java.util.ArrayList
15: 28616 1373568 org.aspectj.weaver.reflect.ShadowMatchImpl
16: 21636 1211616 java.util.LinkedHashMap
17: 16632 1064448 com.mysql.jdbc.ConnectionPropertiesImpl$BooleanConnectionProperty
18: 633 1051448 [Ljava.util.concurrent.ConcurrentHashMap$Node;
19: 13809 994248 java.lang.reflect.Field
20: 30103 963296 java.util.Hashtable$Entry
21: 29188 934016 java.lang.StackTraceElement
22: 21053 918480 [Ljava.lang.String;
23: 28616 915712 org.aspectj.weaver.patterns.ExposedState
24: 55763 892208 java.lang.Object
25: 10309 781000 [S
26: 22238 711616 java.util.ArrayList$Itr
27: 27255 654120 java.lang.StringBuilder
28: 36155 578480 java.lang.Integer
29: 35361 565776 org.apache.ibatis.scripting.xmltags.StaticTextSqlNode
30: 10626 510048 java.nio.HeapByteBuffer
31: 11100 444000 java.util.HashMap$KeyIterator
32: 17617 422808 sun.misc.ProxyGenerator$ConstantPool$IndirectEntry
33: 17379 417096 org.apache.ibatis.scripting.xmltags.IfSqlNode
34: 1367 411912 [Ljava.util.Hashtable$Entry;
35: 10193 407720 java.lang.ref.Finalizer
36: 4992 399360 java.lang.reflect.Constructor
37: 5747 389408 [Ljava.lang.reflect.Method;
- num: 序号
- instances:实例数量
- bytes:占用空间大小
- class name:类名称,[C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][]
jmap -heap
jamp -heap命令可以查看堆信息
# jmap -heap 进程ID > 输出到某个文件
jamp -heap 14660 > ./heap.txt
查看应用在堆内存中的使用情况
Attaching to process ID 54136, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.201-b09
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4236247040 (4040.0MB)
NewSize = 88604672 (84.5MB)
MaxNewSize = 1411907584 (1346.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
Eden Space:
capacity = 763363328 (728.0MB)
used = 685872960 (654.0994262695312MB)
free = 77490368 (73.90057373046875MB)
89.84882228977078% used
From Space:
capacity = 18350080 (17.5MB)
used = 18186488 (17.34398651123047MB)
free = 163592 (0.15601348876953125MB)
99.10849434988839% used
To Space:
capacity = 11534336 (11.0MB)
used = 0 (0.0MB)
free = 11534336 (11.0MB)
0.0% used
PS Old Generation
capacity = 280494080 (267.5MB)
used = 71160960 (67.8643798828125MB)
free = 209333120 (199.6356201171875MB)
25.36986163843458% used
44689 interned Strings occupying 4644264 bytes.
jmap -dump
jmap -dump可以查看堆的快照信息,就是整个实例在堆的使用情况
jmap -dump:format=b,file=test.hprof 14660
导出的堆内存dump文件无法直接查看,可以使用jvisualvm工具查看
可以很清晰的看到目前应用中都创建了哪些对象,哪些对象创建的实例数量和占用内存大小
也可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=./ (路径)
示例代码如下:
public class OOMTest {
public static List<Object> list = new ArrayList<>();
// jvm参数: ‐Xms10M ‐Xmx10M ‐XX:+PrintGCDetails ‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath=D:\jvm.dump
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
int i = 0;
int j = 0;
while (true) {
list.add(new User(UUID.randomUUID().toString(), i++));
new User(UUID.randomUUID().toString(), j--);
}
}
}
如上代码由于会一直创建User对象,并将创建的对象加入到list数组中,终究会导致内存不够,最终会导致内存溢出。那么我们在jvm参数中配置了‐XX:+HeapDumpOnOutOfMemoryError ‐XX:HeapDumpPath这俩参数之后,当触发OOM时就会导出dump文件,我们可以将导出的dump文件装入到jvisualvm工具中分析,是哪个类的实例触发了OOM,如下图所示,我们可以直接发现User类的实例对象创建过多,我们就可以分析我们的业务代码找出问题。
jstack
使用jstack查找死锁
使用jstack加进行ID查找死锁
死锁示例代码
下边代码模拟了死锁程序
public class DeadLockTest {
private static Object object1 = new Object();
private static Object object2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (object1){
try {
System.out.println("thread1 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
System.out.println("thread1 end");
}
}
}).start();
new Thread(() -> {
synchronized (object2) {
try {
System.out.println("thread2 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1){
System.out.println("thread2 end");
}
}
}).start();
System.out.println("main thread end");
}
}
# jstack 线程ID
jstack 14660
“Thread-1” 线程名
prio=5 线程优先级=5
tid=0x0000000002bb2800 java程序的线程id
nid=0x2e40 线程对应的本地线程标识nid(操作系统的线程id)
java.lang.Thread.State: BLOCKED 线程状态(BLOCKED 阻塞)
查看jstack命令输出的信息的最下方会有死锁的具体信息,如下图所示,Found one Jave-level deadlock(发现了一个java级别的死锁)
发现死锁之后我们可以修改业务代码。
使用jstack找出占用cpu最高的线程堆栈信息
package com.pj.jvm;
/**
* 运行此代码,cpu会飙高
*/
public class Math {
public static int initData = 666;
public int compute(){
int a = 1;
int b = 2;
int c = (a + b) * 100;
return c;
}
public static void main(String[] args) {
Math math = new Math();
while (true){
math.compute();
}
}
}
将把上述代码进行编译上传到linux环境的服务器当中,并运行代码
java com.pj.jvm.Math
使用top命令查看cpu运行情况
图中会发现PID为9764的进程CPU使用率为100%,说明这个进程有问题。
具体操作步骤如下:
使用top -p 进程ID,显示java进程的内存情况
top -p 9764
按H,获取每个线程的内存情况
如图中找到内存和cpu占用最高的线程tid,比如9765
然后将得到10进制的9765线程ID,转换为16进制的线程ID,得到16进制的线程ID2624
执行 jstack 9764|grep -A 10 2625,得到线程堆栈信息中 2625这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调用方法
# jstack 进程ID|grep -A 10 十六进制线程ID
jstack 9764|grep -A 10 2625
如图中直接给你定位到了令CPU飙升的代码位置。
jinfo
使用jinfo命令查看正在运行的Java应用程序的扩展参数
查看jvm的参数,除了你配置的jvm参数以外还会把默认jvm参数给你打印出来
jinfo -flags 14124
查看java系统参数
jinfo -sysprops 14124
jstat
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]
jstat -gc 查看内存使用情况及GC情况
jstat -gc pid
jstat -gc pid 最常用,可以评估程序内存使用及GC压力整体情况
S0C:第一个幸存区的大小,单位KB
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小(元空间)
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间,单位s
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间,单位s
GCT:垃圾回收消耗总时间,单位s
堆内存统计
jstat -gccapacity pid
NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0C:第一个幸存区大小
S1C:第二个幸存区的大小
EC:伊甸园区的大小
OGCMN:老年代最小容量
OGCMX:老年代最大容量
OGC:当前老年代大小
OC:当前老年代大小
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代gc次数
FGC:老年代GC次数
新生代垃圾回收统计
jstat -gcnew pid
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
TT:对象在新生代存活的次数
MTT:对象在新生代存活的最大次数
DSS:期望的幸存区大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
新生代内存统计
jstat -gcnewcapacity pid
NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0CMX:最大幸存1区大小
S0C:当前幸存1区大小
S1CMX:最大幸存2区大小
S1C:当前幸存2区大小
ECMX:最大伊甸园区大小
EC:当前伊甸园区大小
YGC:年轻代垃圾回收次数
FGC:老年代回收次数
老年代垃圾回收统计
jstat -gcold pid
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
OC:老年代大小
OU:老年代使用大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
老年代内存统计
jstat -gcoldcapacity pid
OGCMN:老年代最小容量
OGCMX:老年代最大容量
OGC:当前老年代大小
OC:老年代大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
元数据空间统计
jstat -gcmetacapacity pid
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
查看内存使用比例及GC情况
jstat -gcutil pid
S0:幸存1区当前使用比例
S1:幸存2区当前使用比例
E:伊甸园区使用比例
O:老年代使用比例
M:元数据区使用比例
CCS:压缩使用比例
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
在这些命令中可以增加执行时间间隔参数和执行次数,以jstat -gc命令举例
# 每隔1000ms执行10次 jstat -gc 命令
jstat -gc pid 1000 10
JVM运行情况预估
用 jstat gc -pid 命令可以计算出如下一些关键数据,有了这些数据就可以采用之前介绍过的优化思路,先给自己的系统设置一些初始性的JVM参数,比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等。
年轻代对象增长的速率
可以执行命令 jstat -gc pid 1000 10 (每隔1秒执行1次命令,共执行10次),通过观察EU(eden区的使用)来估算每秒eden大概新增多少对象,如果系统负载不高,可以把频率1秒换成1分钟,甚至10分钟来观察整体情况。注意,一般系统可能有高峰期和日常期,所以需要在不同的时间分别估算不同情况下对象增长速率。
Young GC的触发频率和每次耗时
知道年轻代对象增长速率我们就能推根据eden区的大小推算出Young GC大概多久触发一次,Young GC的平均耗时可以通过 YGCT/YGC 公式算出,根据结果我们大概就能知道系统大概多久会因为Young GC的执行而卡顿多久。
每次Young GC后有多少对象存活和进入老年代
这个因为之前已经大概知道Young GC的频率,假设是每5分钟一次,那么可以执行命令 jstat -gc pid 300000 10 ,观察每次结果eden,survivor和老年代使用的变化情况,在每次gc后eden区使用一般会大幅减少,survivor和老年代都有可能增长,这些增长的对象就是每次Young GC后存活的对象,同时还可以看出每次Young GC后进去老年代大概多少对象,从而可以推算出老年代对象增长速率。
Full GC的触发频率和每次耗时
知道了老年代对象的增长速率就可以推算出Full GC的触发频率了,Full GC的每次耗时可以用公式 FGCT/FGC 计算得出。
优化思路其实简单来说就是尽量让每次Young GC后的存活对象小于Survivor区域的50%,都留存在年轻代里。尽量别让对象进入老年代。尽量减少Full GC的频率,避免频繁Full GC对JVM性能的影响。