Java class 是使用调试符号编译的,但堆栈跟踪中没有显示行号?

Java class was compiled with debug symbols, yet no line number is shown in stacktrace?

我收到了某人使用我编写的 Java 库的日志,但令人困惑的是,堆栈跟踪没有列出我的方法的行号。

This question 似乎表明 class 是在没有调试符号的情况下编译的,但是如果我从他们正在使用的 JAR 中获取有问题的 .class 文件并且 运行 javap -v 在上面,我可以看到它实际上是用调试符号编译的,并且有一个 LineNumberTable 用于所讨论的方法:

      LineNumberTable:
        line 387: 0
        line 389: 4
        line 391: 11
        line 393: 23
        line 395: 30
        line 397: 62
        line 399: 69
        line 412: 101
        line 413: 107
        line 414: 116
        line 415: 122
        line 416: 134
        line 417: 141
        line 418: 150
        line 419: 156
        line 421: 168
        line 422: 178
        line 423: 192
        line 425: 206
        line 431: 214
        line 428: 217
        line 430: 219
        line 432: 224

所以我的问题就变成了,即使我已经确认 .class 文件具有调试符号,是什么导致行号没有显示在堆栈跟踪中?如果重要的话,这是在 Android 的上下文中。不,它不是 ProGuard 或去除调试符号的东西,因为行号列在堆栈跟踪的其他部分。

所以我想通了。

快速说明,我可能应该在我的问题中提到这一点:有问题的堆栈跟踪不是 crash/exception 的结果,而是打印它以显示线程在 crash/exception 之前的位置看门狗杀死它是因为它没有反应。

  1. 如果不是死锁,至少也是长线程争用造成的
  2. 当一个线程正在等待调用一个 synchronized 方法而另一个线程正在执行另一个 synchronized 方法时,堆栈跟踪看起来像什么不同于 ART 与 JVM!

在 ART 上,顶部堆栈帧将显示为没有行号的方法,但在 JVM 中,它将显示为方法的第一行,有行号。

这是 Android 的 "complete, minimal, reproducible" 示例:

public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        launchThread("TH1");
        sleep(100);
        launchThread("TH2");
        sleep(20);
        dumpThreadTraces();
    }

    void launchThread(String name)
    {
        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                doThings();
            }
        });

        thread.setName(name);
        thread.start();
    }

    synchronized void doThings()
    {
        sleep(1000);
    }

    void dumpThreadTraces()
    {
        Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();

        Set<Thread> threads = traces.keySet();

        for(Thread th : threads)
        {
            if(th.getName().startsWith("TH"))
            {
                logStackTrace(th, traces.get(th));
            }
        }
    }

    void logStackTrace(Thread thread, StackTraceElement[] stackTrace)
    {
        System.out.printf("thread id=%d name=\"%s\"\n", thread.getId(), thread.getName());
        logStackFrames(stackTrace);
    }

    void logStackFrames(StackTraceElement[] stackTrace)
    {
        for (StackTraceElement frame : stackTrace)
        {
            System.out.printf("    at %s\n", frame.toString());
        }
    }

    void sleep(int millis)
    {
        try
        {
            Thread.sleep(millis);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

当运行时,下面会打印到logcat:

I/System.out: thread id=2051 name="TH1"
I/System.out:     at java.lang.Thread.sleep(Native Method)
I/System.out:     at java.lang.Thread.sleep(Thread.java:371)
I/System.out:     at java.lang.Thread.sleep(Thread.java:313)
I/System.out:     at com.domain.helloworld.MainActivity.sleep(MainActivity.java:94)
I/System.out:     at com.domain.helloworld.MainActivity.doThings(MainActivity.java:58)
I/System.out:     at com.domain.helloworld.MainActivity.run(MainActivity.java:48)
I/System.out:     at java.lang.Thread.run(Thread.java:761)
I/System.out: thread id=2052 name="TH2"
I/System.out:     at com.domain.helloworld.MainActivity.doThings(MainActivity.java)
I/System.out:     at com.domain.helloworld.MainActivity.run(MainActivity.java:48)
I/System.out:     at java.lang.Thread.run(Thread.java:761)

注意线程 2 的顶部堆栈跟踪元素的行号是如何不打印的!

at com.domain.helloworld.MainActivity.doThings(MainActivity.java)