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.
- If not a deadlock, it was at least caused by long thread contention
- What a stack trace looks like when a thread is waiting to call a
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)