前置工作,启动一个WEB项目,用jps查看这个应用的进程号;
jmap
用jps命令查询应用的进程号:
执行命令 jmap -histo 53152 > ./jmap.txt 把应用中当前内存中实例个数、类名以及占用内存大小输出到当前目录的文件中,也可以不指定输出文件直接输出在控制台,结果如下
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 47352
可以导出dump文件到当前目录下;
然后用使用jvisualvm软件进行导入显示
也是显示堆内存中的对象个数和大小;
可以设置内存溢出自动导出dump文件:添加启动参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\jvm.dump
public class MainClass1 {
//-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(j--, UUID.randomUUID().toString()));
}
}
}
Jstack
用jstack加进程id查找死锁
死锁代码:
public class MainClass1 {
static Object obj1 = new Object();
static Object obj2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj1) {
System.out.println("thread1 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println("thread1 and");
}
}
}).start();
new Thread(() -> {
synchronized (obj2) {
System.out.println("thread2 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println("thread2 and");
}
}
}).start();
System.out.println("main thread end");
}
}
Thead-1:线程名
prio=5:优先级=5
tid=0x000001f75d288800:线程id
nid=0x9634:线程对应的本地线程nid
java.lang.Thread.State: RUNNABLE :线程状态
下面是检查到的死锁:
jvisualvm也可以检测到死锁;
Jvisualvm远程连接服务器:
启动普通的jar程序JMX端口配置,
java ‐Dcom.sun.management.jmxremote.port=8888 ‐Djava.rmi.server.hostname=192.168.50.60 ‐Dcom.sun.management.jmxremote.ssl=false ‐Dcom.sun.management.jmxremote.authenticate=false ‐jar microservice‐eureka‐server.jar
tomcat的JMX配置:在catalina.sh文件里的最后一个JAVA_OPTS的赋值语句下一行增加如下配置行
JAVA_OPTS=“$JAVA_OPTS ‐Dcom.sun.management.jmxremote.port=8888 ‐Djava.rmi.server.hostname=192.168.50.60 ‐Dcom.sun.ma
nagement.jmxremote.ssl=false ‐Dcom.sun.management.jmxremote.authenticate=false”
jstack找出占用cpu最高线程的堆栈信息:
1、top命令先找出占用最高CUP的进程:
2、top -p
显示出java进程的内存情况
3、按大写H,获取每个线程的CUP的使用情况
得到占用CUP最高的线程pid 10662
把pid转成十六进制29a6,为线程的十六进制id
4、执行jstack 10662|grep -A 10 29a6,得到线程十六进制id为29a6的堆栈信息所在行的后10行;
就能找到对应占用CPU高的代码
Jinfo
查询正在运行java程序的启动扩展参数
jinfo -sysprops 10854查看java的系统参数
Jstat
查看内存的jvm的使用情况及自定义查看jvm内存的变化和GC情况;
Jstat -gc pid:查看jvm内存的使用和GC的情况
S0C:年轻代的servivor0区大小;单位KB
S1C:年轻代的servivor1区大小
SOU:servivor0的已使用大小
S1U:servivor1的已使用大小
EC:eden区的大小
EU:eden区已使用大小
OC:老年代大小
OU:老年代已使用大小
MC:方法区大小
MU:方法区已使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间已使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间,单位S
FGC:Full GC 次数
FGCT: Full GC 消耗时间,单位S
GC: 垃圾回收消耗总时间。
新生代垃圾回收统计:jstat -gcnew 19848
NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0CMX:最大幸存1区大小
S0C:当前幸存1区大小
S1CMX:最大幸存2区大小
S1C:当前幸存2区大小
ECMX:最大伊甸园区大小
EC:当前伊甸园区大小
YGC:年轻代垃圾回收次数
FGC:老年代回收次数
老年代垃圾回收统计:jstat -gcold 19848
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
OC:老年代大小
OU:老年代使用大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
元空间和垃圾回收统计:jstat -gcmetacapacity 19848
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
Jvm内存使用比例和垃圾回收统计:jstat -gcutil 19848
S0:幸存1区当前使用比例
S1:幸存2区当前使用比例
E:伊甸园区使用比例
O:老年代使用比例
M:元数据区使用比例
CCS:压缩使用比例
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
对JVM运行情况进行预估
jstat -gc 19848 1000 10 每隔一秒执行一次命令,共执行10次;
- 计算年轻代的增长速率:通过观察EU来估算每秒Eden区新增长多少对象;如果系统负载不高,可以提高观察频率,把1秒提高到1分钟也是可以的;注意,一般系统可能有高峰期和日常期,所以需要在不同情况不同情况下估算对象的增长速率。
- Yang GC的触发频率和每次耗时:知道了年轻代对象的增长速率和Edun区的大小就能大概计算出Yang GC的出发频率; 通过YGCT/YGC可以计算出每次耗时;
- Yang GC后大概有多少对象存活和有多少对象进入老年代:在这之前要知道Yang GC的触发频率,假设是每5分钟执行一次,那么执行命令 jstat -gc 19848 300000
10,观察每次Eden、suvivor和老年代使用的变化情况,在每次GC之后Eden都会大幅减少,suvivor和老年代都可能增长,这些增长的对象和suvivor的对象就是每次Yang
GC后存货的对象,同时还可以看出每次Yang GC后进入老年代的对象大小,从而得到老年代的增长速率。 - Full GC的触发频率和每次耗时:知道了老年代的增长速率就能计算出Full GC的触发频率;Full GC的每次耗时用FGCT/FGC得出;
- 优化思路:尽量让Yang GC后存活的对象小于suvivor区的50%,这样都会留在年轻代里,尽量别让对象进入老年代,减少Full GC的频率;
内存泄漏是怎么回事:
在实际开发中,常常把一些数据存到JVM缓存中,比如map、List等,随着时间推移JVM缓存使用的越来越大,一直占用老年代的空间,导致频繁的Full GC,这就是一种内存泄漏;对于老旧数据没有及时清理导致一直占用着我们宝贵的内存资源,时间长了除了造成频繁的Full GC还有可能导致OOM;
这种情况完全可以考虑使用一些成熟的JVM级缓存框架来解决,比如ehcache等自带LUR数据淘汰算法的框架来作为JVM级的缓存。