针对两种需求,提供对应的实现方式。
- 输出线程栈信息到命令行。
- 输出线程栈信息到文件。
1.输出线程栈命令行
输出线程栈到命令可以使用如下命令
jcmd <pid> Thread.print
使用如下命令查看帮助文档
jcmd <pid> help Thread.print
# 帮助文档如下
Print all threads with stacktraces.
Impact: Medium: Depends on the number of threads.
Syntax : Thread.print [options]
Options: (options must be specified using the <key> or <key>=<value> syntax)
-l : [optional] print java.util.concurrent locks (BOOLEAN, false)
-e : [optional] print extended thread information (BOOLEAN, false)
常见带参数的命令如下(一般不带参数的命令足够使用)
jcmd 397 Thread.print -l -e
# -l参数可以打印关于锁的更详细的信息, 包括该锁的持有线程, 锁的内存地址, 锁的当前状态
# -e参数可以打印线程的扩展信息,1.该线程当前持有的锁和正在等待的锁; 2.不仅包括Java栈帧还输出涉及本地方法调用和其他系统级栈帧。
2.将线程栈导出到文件
jdk21以上版本
将线程栈输出到文件可以使用如下命令
# 将线程栈导出到stackinfo.txt文件当中
jcmd <pid> Thread.dump_to_file stackinfo.txt
使用如下命令查看帮助文档
jcmd <pid> help Thread.dump_to_file
# 帮助文档如下
Thread.dump_to_file
Dump threads, with stack traces, to a file in plain text or JSON format.
Impact: Medium: Depends on the number of threads.
Syntax : Thread.dump_to_file [options] <filepath>
Arguments:
filepath : The file path to the output file (STRING, no default value)
Options: (options must be specified using the <key> or <key>=<value> syntax)
-overwrite : [optional] May overwrite existing file (BOOLEAN, false)
-format : [optional] Output format ("plain" or "json") (STRING, plain)
可以使用如下这样的一些命令
# 将线程栈导出到stackinfo.txt文件当中
jcmd <pid> Thread.dump_to_file stackinfo.txt
# 和上面命令等价, 默认就是"-format=plain"
jcmd <pid> Thread.dump_to_file -format=plain stackinfo.txt
# 使用json格式输出线程栈并导出到stackinfo.txt文件当中
jcmd <pid> Thread.dump_to_file -format=json stackinfo.txt
jdk21以下版本(jdk8/jdk17/…)
jdk21以下,没有Thread.dump_to_file命令,只能使用Thread.print命令。
可以通过重定向的方式写入到文件
jcmd <pid> Thread.print > jstack.txt
3.基于SpringBootActuator暴露JStack端点
@WebEndpoint(id = "jstack")
public class JStackEndpoint {
private static final Logger logger = LoggerFactory.getLogger(JStackEndpoint.class);
/**
* 加锁
*/
private final Lock lock = new ReentrantLock();
private final int timeoutSeconds;
public JStackEndpoint() {
this(10);
}
public JStackEndpoint(int timeoutSeconds) {
this.timeoutSeconds = timeoutSeconds;
}
@ReadOperation
public ResponseEntity<Object> doJStack(@Nullable String type, @Nullable String fileName) {
final String typeToUse = Optional.ofNullable(type).orElse("file");
final String fileNameToUse = Optional.ofNullable(fileName).orElse("stackinfo.txt");
final String resourcePath = String.format("/tmp/%s", fileNameToUse);
try {
if (lock.tryLock(timeoutSeconds, TimeUnit.SECONDS)) {
try {
// 在Java10可以通过RuntimeMXBean直接获取pid, Java8只能通过切取
final String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
final ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("jcmd", pid, "Thread.print", "-l", "-e");
final Process process = processBuilder.start();
try (InputStream eis = process.getErrorStream(); InputStream is = process.getInputStream()) {
final JStackResult result;
if ("text".equals(typeToUse)) {
result = new JStackResult(StreamUtils.copyToString(is, StandardCharsets.UTF_8), MediaType.TEXT_MARKDOWN);
} else if ("file".equals(typeToUse)) {
Files.deleteIfExists(Paths.get(resourcePath));
try (OutputStream os = new FileOutputStream(resourcePath, false)) {
StreamUtils.copy(is, os);
result = new JStackResult(new FileSystemResource(resourcePath), MediaType.APPLICATION_OCTET_STREAM);
}
} else {
result = new JStackResult(String.format("Unsupported type(%s) for jstack endpoint", typeToUse), MediaType.TEXT_PLAIN);
}
// 必须先读缓冲区, 再waitFor等待进程退出
if (process.waitFor() != 0) {
throw new RuntimeException(String.format("Wait jcmd process error, %s", StreamUtils.copyToString(eis, StandardCharsets.UTF_8)));
}
return ResponseEntity.ok()
// 下载附件的文件名称(使用WebEndpointResponse实现不了这个功能)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileNameToUse + "\"")
.contentType(result.mediaType)
.body(result.result);
}
} finally {
lock.unlock();
}
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("jstack lock error");
} catch (Throwable ex) {
logger.error("do jstack error", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
static class JStackResult {
private final Object result;
private final MediaType mediaType;
public JStackResult(Object result, MediaType mediaType) {
this.result = result;
this.mediaType = mediaType;
}
}
}