查看启动的 java 进程

看内存信息,实例个数以及占用内存大小

[C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][]
jump -heap 17680

jmap -dump:format=b,file=./j.hprof 17680
导出是二进制,可以使用 jdk 自带的 jvisualvm.exe 导入查看


通常设置内存溢出自动导出 dump 文件(内存很大的时候,可能会导不出来)
有如下示例会产生一个死锁:
package io.dc;
public class DeadLockTest {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
try {
System.out.println("thread1 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
}
synchronized (lock2) {
System.out.println("thread1 end");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
try {
System.out.println("thread2 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
}
synchronized (lock1) {
System.out.println("thread2 end");
}
}
}).start();
System.out.println("main thread end");
}
}
可以使用 jstack PID 查看:


直接可以定位到代码的大致位置
有如下程序会导致 cpu 飙升:
public class Math {
public static final int initData = 666;
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
while (true) {
// 导致CPU飙升
math.compute();
}
}
}
在 Linux 使用 top 命令,发现 java 进程 CPU 占用高:

使用 top -p 105654 ,然后按 H , 查看这个进程的详细信息:

可以看到线程 105655 占用资源比较高,将 105655 转换成 16 进制: 19cb7
执行 jstack 105654 | grep -A 10 "19cb7" 查看此线程的相关信息:

可以找到问题代码的大致位置
查看正在运行的 java 程序的扩展参数
jinfo -flags 105654

jinfo -sysprops 105654

可以查看堆内存各部分使用量,以及加载类的数量
使用方法:
jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]
jstat -gc PID

jstat -gccapacity PID

jstat -gcnew PID

jstat -gcnewcapacity PID

jstat -gcold PID

jstat -gcoldcapacity PID

jstat -gcmetacapacity PID

jstat -gcutil PID

知道了如何统计 jvm 运行的信息,就可以根据现有的信息预估以后程序占用资源的走向,从而调整合理的 jvm 参数,比如: 堆内存大小,年轻代,老年代大小, Eden 和 Survivor 的比例,大对象的阀值,进入老年代年龄的阀值等
可以执行 jstat -gc PID 1000 20 观察 EU 区估算每秒新增多少对象,一般系统又高峰期和非高峰期,需要在不同时间段分别统计
知道年轻代对象的增长速率,可以预估 Young GC 多久触发一次,Young GC 的平均耗时可以 YGCT / YGC 算出,这两个结果可以知道系统大概多久系统会因为 Young GC 卡顿多久
每次 GC 后,Eden 区会大幅度减少,survivor 和老年代都会有增长,这些增长的对象就是 Young GC 后存活的对象,同时还可以看出每次进入老年代的对象,这就是老年代对象的增长速率
知道了老年代的增长速率,就可以估算出 Full GC 的触发频率了,每次耗时可以通过 FGCT / FGC 算出
尽量让每次 Young GC 之后的存活对象小于 Survivor 区域的 50%,尽量别让对象进入老年代,尽量减少 Full GC 的频率,避免频繁 Full GC 堆 jvm 性能的影响
一般是频繁创建大对象,导致老年代的占用极速增加,这部分代码可能占用 CPU 比较高。
一般电商架构可能会使用多级缓存架构,就是redis加上JVM级缓存,大多数同学可能为了图方便对于JVM级缓存就 简单使用一个hashmap,于是不断往里面放缓存数据,但是很少考虑这个map的容量问题,结果这个缓存map越来越大,一直占用着老 年代的很多空间,时间长了就会导致full gc非常频繁,这就是一种内存泄漏,对于一些老旧数据没有及时清理导致一直占用着宝贵的内存 资源,时间长了除了导致full gc,还有可能导致OOM。 这种情况完全可以考虑采用一些成熟的JVM级缓存框架来解决,比如ehcache等自带一些LRU数据淘汰算法的框架来作为JVM级的缓存
Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6+, 采用命令行交互模式,可以方便的定位和诊断 线上程序运行问题。Arthas 官方文档十分详细 https://arthas.aliyun.com/doc/manual-install.html?userCode=okjhlpr5