一、线上问题的排查
进程ID 简称为PID
free -m
查看内存使用情况iostat
查看磁盘读写活动情况netstat
查看网络连接情况df -h
查看磁盘空间使用情况du -sh
查看文件大小情况
1.1、top 命令查看CPU占用情况
top -n num 查看CPU占用最高的num个进程
top -Hp PID 或 top -H -p PID 查看该进程号的所有线程CPU与内存占用情况,找到占用最多的线程ID
1.2、jstack 命令查看Java线程信息
生成 Java 虚拟机当前时刻的线程快照
jps -l 显示当前所有 Java 进程ID 的命令 (window环境和linux环境)
printf '%x\n' PID 或 printf %x PID表示将线程ID 转换为十六进制,用于搜索线程堆栈中的关键信息 (linux环境)
jstack PID | grep 线程ID 查看线程堆栈信息 (linux环境)
jstack PID | grep -C10 线程ID --color 查看线程堆栈信息 (linux环境)
jstack -l PID 查看线程堆栈信息
jstack PID >> stack.txt 将当前所有堆栈信息输出到stack.txt文件中
1.3、jstat 命令查看GC信息
jstat -gcutil PID 1000 2 或 jstat -gc PID 1000 2 表示进程ID每间隔1000毫秒统计2次(缺省代表一直统计),查看某进程GC持续变化情况。
gcutil 的意思是[已使用空间]占[总空间]的百分比。
新生代Eden区(E,表示Eden)使用了65.33%(最后)的空间
两个Survivor区(S0、S1,表示Survivor0、Survivor1)分别是0和94.27%
老年代(O,表示Old)使用了63.80%
程序运行以来共发生Minor GC(YGC,表示Young GC)53次,总耗时1.189秒(YGCT 表示耗时),
发生Full GC(FGC,表示Full GC)4次,总耗时0.739秒(FGCT 表示耗时)
总的耗时(GCT,表示GC Time)为1.929秒。
1.4、 jmap 命令分析内存
JDK中提供用来监视进程运行中的jvm物理内存的工具。
该进程内存中所有对象的情况,例如产生了哪些对象,对象数量。
当系统崩溃时,jmap 可以从core文件或进程中获得内存的具体匹配情况,包括Heap size, Perm size等。
注意:使用jmap dump堆信息时,会触发Full GC, 触发Full GC 可能导致线上服务不可用。
如果想dump堆信息,可以使用gcore命令,比jmap -dump快。
jmap -heap PID 查看进程的JVM占用内存情况
jmap -histo:live PID 显示堆中当前活动的所有对象的统计信息,按实例对象数量从高到低显示。重点关注实例对象数量过多的类。并找到对应程序。
jmap -histo PID | head -n 10 查看前10的对象统计信息(仅用于linux, window执行不了)
jmap -dump:format=b,file=heap.dump PID 生成堆转储快照dump文件(即:导出堆信息)
jmap -dump:format=b,file=heap.hprof PID 生成堆转储快照dump文件(即:导出堆信息)
1.5、 arthas工具
线上监控诊断产品,通过全局视角实时查看应用 load、内存、GC、线程的状态信息,
在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等。
1.下载arthas 只要解压,就能直接使用(window 和 linux使用方式相同)
2.linux环境 , 进入 arthas 目录下,输入java -jar arthas-boot.jar
3.window 环境,在 arthas 目录下,按住shift + 右击,打开命令窗口,输入上面的命令。
4.输入 java 项目前面的序号(即:数字)
dashboard 查看总体概况,发现有CPU占用高的进程
thread PID 可以查看指定线程信息,能迅速定位到问题代码
1.6、 异常情况解决总结
GC问题: top + top -Hp + jstack 排查是"VM Thread"消耗过多资源,可以进一步使用 jmap 工具进行内存溢出排查。
业务执行过慢问题: top + top -Hp + jstack 排查发现是普通业务线程,可看到具体是哪个接口。
死锁: jstack + Java 进程打印堆栈信息中包含死锁信息deadlock
线程处于waiting状态: 多打印几次``jstack` 信息,对比一直停留在waiting状态的线程。
二、节省内存的建议
2.1、使用原始数据类型
int x = 42; // 使用 int 而不是 Integer
double d = 3.14; // 使用 double 而不是 Double
boolean b = true; // 使用boolean而不是Boolean
2.2、避免不必要的对象创建
String s = "Hello" + " World"; // 改为使用 StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
String s = sb.toString();
2.3、使用惰性初始化
private List<String> myList;
if (myList == null) {
myList = new ArrayList<>();
}
2.4、重用对象
List<String> tempList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
tempList.add("Item " + i);
// 用 tempList 做点什么
}
tempList.clear(); // 清除列表以供重用
for (int i = 0; i < 1000; i++) {
tempList.add("Another item " + i);
// 用 tempList 做点什么
}
2.5、循环外申明对象,循环内创建对象。
User user;
for (int i = 0; i < 1000; i++) {
user = new User();
// 用 user 做点什么
}
2.6、使用数组而不是集合
String[] array = new String[1000];
for (int i = 0; i < 1000; i++) {
array[i] = "Item " + i;
// 用数组做点什么
}
2.7、尽量使用移位来代替'a*b'的操作
同样的,对于'*'操作,使用移位的操作将会更快和更有效
如
int num = a * 4;
int num = a * 8;
应该改为
int num = a << 2;
int num = a << 3;
2.8、避免使用同步
同步会影响Java程序的性能,因为它会导致线程的切换和等待。在多线程环境中,可以使用volatile、Atomic类或者Concurrent数据结构来保证线程安全。
2.9、尽量使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。