【全网首发】一次jstack 的排查之旅原创
前言
今天阳光明媚,天气真好,奈何是工作日,不然大睡一场,岂不美哉。
还残留在昨天的美梦中,突然群内的告警把我拉回了现实,奈何还在车上,不能拿上电脑,疯狂输出。
群内告警,接口频繁超时,运维查看了机器情况,超时的接口的应用的CPU,居高不下,如下图
好家伙,一大早就给我喂饭,其实这个问题也没什么,jstack打印下,再下来根据日志分析分析,多半能找到原因。
但是奇怪的问题是,运维在使用jstack打印的时候出现了问题,如下图,这就有点意思了,让我深入的分析了下jstack。
是的,出现了connection refused
导致jstack打印没成功,为了不影响客户的使用,紧急重启,此时我还在骑马的路上。
- 系统环境:centos6
- java版本:1.6.0_24
分析
jstack打印出现connection refused
我倒是第一次见,不知道什么原因,那就只有谷歌了。不知道是不是我检索的方式有问题,没有找到有用的信息。那既然谷歌找不到有用的信息,那就只能看源代码了
- 下载jdk1.6的源代码
找到sun.tools.jstack.JStack
这个类,这里面的代码并不复杂。
- 这里是方法的入口,大概意思,就是解析jstack -xx后面的参数,需要注意点就是使用-F -m 和不使用-F -m的时候走的逻辑是不一样的。-F -m的时候useSA =true
if (args.length == 0) {
usage(); // no arguments
}
boolean useSA = false;
boolean mixed = false;
boolean locks = false;
// Parse the options (arguments starting with "-" )
int optionCount = 0;
while (optionCount < args.length) {
String arg = args[optionCount];
if (!arg.startsWith("-")) {
break;
}
if (arg.equals("-F")) {
useSA = true;
} else {
if (arg.equals("-m")) {
mixed = true;
} else {
if (arg.equals("-l")) {
locks = true;
} else {
usage();
}
}
}
optionCount++;
}
- 由于上面是没有使用任何参数,所以直接看不带-F的逻辑
if (useSA) {
// parameters (<pid> or <exe> <core>
String params[] = new String[paramCount];
for (int i=optionCount; i<args.length; i++ ){
params[i-optionCount] = args[i];
}
runJStackTool(mixed, locks, params);
} else {
// pass -l to thread dump operation to get extra lock info
String pid = args[optionCount];
String params[];
if (locks) {
params = new String[] { "-l" };
} else {
params = new String[0];
}
runThreadDump(pid, params);
}
- 查看
runThreadDump
的方法,这里的注意逻辑在于remoteDataDump
方法
private static void runThreadDump(String pid, String args[]) throws Exception {
VirtualMachine vm = null;
try {
// 1.attach 传入进来的pid
vm = VirtualMachine.attach(pid);
} catch (Exception x) {
String msg = x.getMessage();
if (msg != null) {
System.err.println(pid + ": " + msg);
} else {
x.printStackTrace();
}
if ((x instanceof AttachNotSupportedException) &&
(loadSAClass() != null)) {
System.err.println("The -F option can be used when the target " +
"process is not responding");
}
System.exit(1);
}
// Cast to HotSpotVirtualMachine as this is implementation specific
// method.
//
2. remote dump
InputStream in = ((HotSpotVirtualMachine)vm).remoteDataDump((Object[])args);
// read to EOF and just print output
3. 输出dump的信息
byte b[] = new byte[256];
int n;
do {
n = in.read(b);
if (n > 0) {
String s = new String(b, 0, n, "UTF-8");
System.out.print(s);
}
} while (n > 0);
in.close();
vm.detach();
}
- 调用
remoteDataDump
public InputStream remoteDataDump(Object ... args) throws IOException {
return executeCommand("threaddump", args);
}
- 调用
executeCommand
private InputStream executeCommand(String cmd, Object ... args) throws IOException {
try {
return execute(cmd, args);
} catch (AgentLoadException x) {
throw new InternalError("Should not get here", x);
}
}
-
这里的
execute
有几种实现,我们查看linux的实现
-
linux平台的
execute
的实现
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
assert args.length <= 3; // includes null
// did we detach?
String p;
synchronized (this) {
if (this.path == null) {
throw new IOException("Detached from target VM");
}
p = this.path;
}
// create UNIX socket
// 创建unix socket
int s = socket();
// connect to target VM
try {
connect(s, p);
} catch (IOException x) {
close(s);
throw x;
}
IOException ioe = null;
// connected - write request
// <ver> <cmd> <args...>
try {
writeString(s, PROTOCOL_VERSION);
writeString(s, cmd);
for (int i=0; i<3; i++) {
if (i < args.length && args[i] != null) {
writeString(s, (String)args[i]);
} else {
writeString(s, "");
}
}
} catch (IOException x) {
ioe = x;
}
// Create an input stream to read reply
SocketInputStream sis = new SocketInputStream(s);
// Read the command completion status
int completionStatus;
try {
completionStatus = readInt(sis);
} catch (IOException x) {
sis.close();
if (ioe != null) {
throw ioe;
} else {
throw x;
}
}
if (completionStatus != 0) {
sis.close();
// In the event of a protocol mi**atch then the target VM
// returns a known error so that we can throw a reasonable
// error.
if (completionStatus == ATTACH_ERROR_BADVERSION) {
throw new IOException("Protocol mi**atch with target VM");
}
// Special-case the "load" command so that the right exception is
// thrown.
if (cmd.equals("load")) {
throw new AgentLoadException("Failed to load agent library");
} else {
throw new IOException("Command failed in target VM");
}
}
// Return the input stream so that the command output can be read
return sis;
}
- 在这里我们已经可以看到前面出现的
connection refused
大概是因为连接的时候失败了,然后出现的异常。
思考
通过上面的分析,我们都已经知道了为什么出现connection refused
,但是我们怎么在出现connection refused
的时候,获取到更多的java信息,以便后续分析问题呢。
上面的源代码有提到,使用jstack 带-F 和不带-F的时候走的是不同的逻辑,那我们就看看带-F的逻辑吧
- -F的时候
useSA
为true,所以这里会执行runJStackTool
1.mixed 使用-F的时候有没有带-m,有为true,没有是false
2.locks 使用-F的时候有没有带-l,有为true,没有是false
3.params 使用-标志后的其他参数,通常是pid
使用jstack -h
的时候,可以看到jstack的选项说明
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
if (useSA) {
// parameters (<pid> or <exe> <core>
String params[] = new String[paramCount];
for (int i=optionCount; i<args.length; i++ ){
params[i-optionCount] = args[i];
}
runJStackTool(mixed, locks, params);
} else {
// pass -l to thread dump operation to get extra lock info
String pid = args[optionCount];
String params[];
if (locks) {
params = new String[] { "-l" };
} else {
params = new String[0];
}
runThreadDump(pid, params);
}
runJStackTool
方法,使用反射去执行sun.jvm.hotspot.tools.JStack
的main
方法进行dump
private static void runJStackTool(boolean mixed, boolean locks, String args[]) throws Exception {
Class<?> cl = loadSAClass();
if (cl == null) {
usage(); // SA not available
}
// JStack tool also takes -m and -l arguments
if (mixed) {
args = prepend("-m", args);
}
if (locks) {
args = prepend("-l", args);
}
Class[] argTypes = { String[].class };
Method m = cl.getDeclaredMethod("main", argTypes);
Object[] invokeArgs = { args };
m.invoke(null, invokeArgs);
}
- 查看
sun.jvm.hotspot.tools.JStack
的main
方法
public static void main(String[] args) {
boolean mixedMode = false;
boolean concurrentLocks = false;
int used = 0;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-m")) {
mixedMode = true;
used++;
} else if (args[i].equals("-l")) {
concurrentLocks = true;
used++;
}
}
if (used != 0) {
String[] newArgs = new String[args.length - used];
for (int i = 0; i < newArgs.length; i++) {
newArgs[i] = args[i + used];
}
args = newArgs;
}
JStack jstack = new JStack(mixedMode, concurrentLocks);
jstack.start(args);
jstack.stop();
}
- 查看run方法
public void run() {
Tool tool = null;
if (mixedMode) {
tool = new PStack(false, concurrentLocks);
} else {
tool = new StackTrace(false, concurrentLocks);
}
tool.setAgent(getAgent());
tool.setDebugeeType(getDebugeeType());
tool.run();
}
在这里我们理清了jstack大概的逻辑,下面来实践
实践
- jstack -m 打印混合模式(Java 和本机 C/C++ 帧)堆栈跟踪。
- jstack -F 当没有响应时强制打印堆栈
在jdk1.6.0_24的时候使用jstack -F 会有问题,这是一个已知的jdk的bug,
hread 26724: (state = BLOCKED)
Error occurred during stack walking:
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: get_thread_regs failed for a lwp
at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$LinuxDebuggerLocalWorkerThread.execute(LinuxDebuggerLocal.java:152)
at sun.jvm.hotspot.debugger.....
设置export LIBSAPROC_DEBUG=1,再次使用jstack -F pid,会发现多了一行
Thread 26724: (state = BLOCKED)
libsaproc DEBUG: ptrace(PTRACE_GETREGS, ...) not supported
这里是因为缺少libsaproc.so文件,jdk1.6.30已经修复
我本来想下载个jdk1.6.30的新版本,但是oracle下载jdk居然要登录,然后直接把jdk1.8下的拷贝到jdk.1.6下来也能用
cp jdk1.8.0_77/jre/lib/amd64/libsaproc.so jdk1.6/jre/lib/amd64/libsaproc.so
总结
在出现以上的情况下,我们可以使用
- jstack -F pid
- jstack -m pid
- pstack pid