获取Java进程
jps -l
根据进程获取线程信息并保存为文件
jstack pid > pid.txt
从服务器下载到本地
sz pid.txt
服务器上运行 top -Hp pid
命令,通过查看该进程下的所有的线程数据,来查看进程中哪个线程 CPU 使用高,然后,输入大写的 P 将线程按照 CPU 使用率排序
获取的PID是十进制的,需要查找十六进制的printf “%x\n” 十进制的线程id
最后,在 jstack 命令输出的线程栈中搜索这个线程 ID,定位出问题的线程当时的调用栈
调整运行堆为1G,即1 * 1024 * 1024 * 1024字节
java -Xms1g -Xmx1g -jar ErrorMaker-0.0.1-SNAPSHOT.jar
jps -l
jinfo pid查看
服务器上主要使用命令行工具,如果希望看到GC趋势的话,可以使用jstat工具,jstat工具允许以固定的监控频次输出JVM的各种监控指标,比如使用-gcutil输出GC和内存占用汇总信息,每隔5秒输出一次,输出100次,可以看到Young GC比较频繁,而Full GC基本10秒一次:
其中,S0 表示 Survivor0 区占用百分比,S1 表示 Survivor1 区占用百分比,E 表示Eden 区占用百分比,O 表示老年代占用百分比,M 表示元数据区占用百分比,YGC 表示年轻代回收次数,YGCT 表示年轻代回收耗时,FGC 表示老年代回收次数,FGCT 表示老年代回收耗时。
通过命令行工具 jstack,也可以实现抓取线程栈的操作:
抓取后可以使用类似fastthread这样的在线分析工具来分析线程栈。
长度最长的Mysql本地查询进行分析
内存溢出OOM
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@RestController
public class OOMMakerController {
List<String> data = new ArrayList<>();
/**
* 加启动参数:-Xms256m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=.
*/
@GetMapping("oom")
public void oom() {
while(true) {
data.add(IntStream.rangeClosed(1, 10_000)
.mapToObj(i -> "a")
.collect(Collectors.joining("")));
}
}
}
加启动参数:-Xms256m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=.
MAT工具查看Spring容器的加载bean
超多的对象
OQL查询
Arthas定位问题
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
import org.springframework.util.DigestUtils;
import java.lang.management.ManagementFactory;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class HighCPUApplication {
private static int ADMIN_ID = 0;
private static byte[] payload = IntStream.rangeClosed(1, 10_000)
.mapToObj(i -> "a")
.collect(Collectors.joining(""))
.getBytes(StandardCharsets.UTF_8);
private static Random random = new Random();
private static void doTask(int i) {
if (i != ADMIN_ID) {
IntStream.rangeClosed(1, 10_000).parallel().forEach(j -> DigestUtils.md5DigestAsHex(payload));
}
}
public static void main(String[] args) {
System.out.println("HighCPUApplication VM options");
System.out.println(ManagementFactory.getRuntimeMXBean().getInputArguments()
.stream()
.collect(Collectors.joining(System.lineSeparator())));
System.out.println("HighCPUApplication Program arguments");
System.out.println(Arrays.stream(args).collect(Collectors.joining(System.lineSeparator())));
while (true) {
doTask(random.nextInt(100));
}
}
}
使用Java8打印的线程信息更清晰
/Users/zhouguangping/Library/Java/JavaVirtualMachines/corretto-1.8.0_302/Contents/Home/bin/java -jar ErrorMaker-1.0-SNAPSHOT.jar
/Users/zhouguangping/Library/Java/JavaVirtualMachines/corretto-1.8.0_302/Contents/Home/bin/java -jar arthas-boot.jar
线程信息
主线程
可以看到,由于这些线程都在处理 MD5 的操作,所以占用了大量 CPU 资源。我们希望分析出代码中哪些逻辑可能会执行这个操作,所以需要从方法栈上找出我们自己写的类,并重点关注。
使用 jad 命令直接对 HighCPUApplication 类反编译: jad org.me.HighCPUApplication