I've received a log from someone using a Java library that I wrote, but perplexingly the stacktrace does not list the line number of my method.
This question seems to indicate that means the class was compiled without debug symbols, but if I take the .class
file in question from the JAR they're using and run javap -v
on it, I can see that was in fact compiled with debug symbols, and there's a LineNumberTable for the method in question:
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
So my question then becomes, what could cause the line number to not show up in the stacktrace even though I've confirmed that the .class
file has the debug symbols? If it matters, this is in the context of Android. And no, it isn't ProGuard or something stripping out debug symbols, because line numbers are listed in other parts of the stack trace.
So I figured this out.
A quick note, and I probably should have mentioned this in my question: the stack trace in question was not the result of a crash/exception but rather it was printed to show where the thread was at before the watchdog killed it because it was unresponsive.
synchronized
method when another thread is executing another synchronized
method differs from ART vs. the JVM!On ART, the top stack frame will be shown as in the method without a line number, but in the JVM it will be shown as the first line in the method with a line number.
Here's a "complete, minimal, reproducible" example for Android:
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();
}
}
}
When run, the following will be printed to 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$1.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$1.run(MainActivity.java:48)
I/System.out: at java.lang.Thread.run(Thread.java:761)
Notice how for thread 2, the line number for the top stack trace element is not printed!
at com.domain.helloworld.MainActivity.doThings(MainActivity.java)