MaxJavaStackTraceDepth是干嘛的?

编译AOSP源码是发现需要这么设置jack-server: JACK_SERVER_COMMAND="java -XX:MaxJavaStackTra…
关注者
11
被浏览
8,290

1 个回答

Jack是给Android用的、用Java写的一个把Java源码编译到DEX格式的编译器。而这个编译器自身通常是在Oracle JDK / OpenJDK的HotSpot VM上运行的。这里涉及的参数就是一个HotSpot VM特有的参数。

题主所说的代码是从这个changeset放进去的:

android.googlesource.comandroid-review.googlesource.com

从commit message和review thread可以看到,作者加入 -XX:MaxJavaStackTraceDepth=-1 参数的目的是让JVM在实现 java.lang.Throwable.fillInStackTrace() 时把整个调用栈上所有Java栈帧的信息都记录下来。

HotSpot VM的 -XX:MaxJavaStackTraceDepth 参数的默认值是1024,也就是说 fillInStackTrace() 在记录到最多1024层调用栈帧信息后就会停止爬栈。这是因为爬栈并将栈帧信息转换为Java对象(StackTraceElement)是个比较慢的动作,限制一下最大深度可以把 fillInStackTrace() 的最大开销给限制住。

  product(intx, MaxJavaStackTraceDepth, 1024,                               \
          "The maximum number of lines in the stack trace for Java "        \
          "exceptions (0 means all)")                                       \

我们可以用一个小实验来演示一下:

public class StackTraceDepthDemo {
  public static void demo(int depth) {
    if (depth > 0) demo(depth - 1);
    else throw new RuntimeException("Count the stack trace depth");
  }

  public static void main(String[] args) {
    demo(2047);
  }
}

这个例子运行的时候,如果 fillInStackTrace() 能收集整个调用栈上的所有Java栈帧的信息,那么我们应该能看到2049层栈帧(2048层 demo() + 1层 main()),其中最底下的一行应该是 at StackTraceDepthDemo.main() 。

在Mac OS X上用Oracle JDK8u101以默认参数来运行:

$ java StackTraceDepthDemo 2>&1 | grep 'at StackTraceDepthDemo' | wc -l
    1024

看,只爬到了1024层栈帧。如果我们按照题主引用的代码去配置参数:

$ java -XX:MaxJavaStackTraceDepth=-1 StackTraceDepthDemo 2>&1 | grep 'at StackTraceDepthDemo' | wc -l
    2049
$ java -XX:MaxJavaStackTraceDepth=-1 StackTraceDepthDemo 2>&1 | tail -n1
	at StackTraceDepthDemo.main(StackTraceDepthDemo.java:8)

这样就爬到了整个调用栈的所有Java栈帧了。

原理:

其实这算是HotSpot VM的一个bug,不过是bug被当作feature来用了。

前面提到了 MaxJavaStackTraceDepth 参数的声明,它被声明为 intx 类型,这其实就是 intptr_t ,所以是带符号的、指针宽度的整型。所以它当然可以被设置为负数。

$ java -XX:+PrintFlagsFinal -XX:MaxJavaStackTraceDepth=-1 -version | grep MaxJavaStackTraceDepth
     intx MaxJavaStackTraceDepth                   := -1                                  {product}
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

但按照这个参数原本的设计意图,它应该只允许非负数的值,所以本来它的声明应该用 uintx (就是uintptr_t)而不应该用 intx 。这就是bug的源头所在。

HotSpot VM里, java.lang.Throwable.fillInStackTrace() 的native实现有如下的结构:

void java_lang_Throwable::fill_in_stack_trace(Handle throwable, methodHandle method, TRAPS) {
  if (!StackTraceInThrowable) return;
  
  // ...

  int max_depth = MaxJavaStackTraceDepth;
  JavaThread* thread = (JavaThread*)THREAD;
  BacktraceBuilder bt(CHECK);

  // ...
  int total_count = 0;

  for (frame fr = thread->last_frame(); max_depth != total_count;) {
    // ...
    bt.push(method, bci, CHECK);
    total_count++;
  }
  // ...
}

可以看到,这里会循环遍历整个调用栈上的所有栈帧,直到 max_depth == total_count 。然而 total_count 是从0开始向上数的,除非已经到整数溢出,不然它一直递增上去的话是无论如何也不会跟配置为 -1 的 max_depth 相等,所以就达到了“不设限制,把整个调用栈的所有栈帧都爬了”的目的。

然而如果HotSpot VM把这个bug给修了,把 MaxJavaStackTraceDepth 的声明改为 uintx 类型的话,配置为 -1 就没用了——HotSpot VM在处理命令行参数的时候如果要对 uintx 类型的参数配置负值的话,这个配置的值会直接被忽略。

如果非要爬整个栈,比较好的做法是把 MaxJavaStackTraceDepth 配置为一个很大的正整数,例如说 INT32_MAX ,通常也就够用了。