#06 pc 0002b5c4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184) [armeabi-v7a]
#07 pc 0005fc79 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+272) [armeabi-v7a]
#08 pc 0005fca3 /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, …)+20) [armeabi-v7a]
#09 pc 0005481f /system/lib/libdvm.so [armeabi-v7a]
#10 pc 0000e3e8 /system/lib/libc.so (__thread_entry+72) [armeabi-v7a]
#11 pc 0000dad4 /system/lib/libc.so (pthread_create+160) [armeabi-v7a]
APP 收到 SIGSTKFLT 信号崩溃了,同时还输出了这样的日志:
06-25 15:10:53.821 7449 7450 E dalvikvm: threadid=2: stuck on threadid=135, giving up
06-25 15:10:53.821 7449 7450 D dalvikvm: threadid=2: sending two SIGSTKFLTs to threadid=135 (tid=8021) to cause debuggerd dump
SIGSTKFLT 是 Dalvik 虚拟机特有的一个信号。当虚拟机发生了 ANR 或者需要做 GC 的时候,就需要挂起所有 RUNNING 状态的线程,如果此时 Dalvik 虚拟机等待了足够长时间,线程仍旧无法被挂起,就会调用dvmNukeThread
函数发送 SIGSTKFLT 信号给相应线程,从而杀死 APP。
具体代码如下:
static void waitForThreadSuspend(Thread* self, Thread* thread)
{
const int kMaxRetries = 10;
… …
while (thread->status == THREAD_RUNNING) {
… …
if (retryCount++ == kMaxRetries) {
ALOGE(“Fatal spin-on-suspend, dumping threads”);
dvmDumpAllThreads(false);
/* log this after – long traces will scroll off log */
=> ALOGE(“threadid=%d: stuck on threadid=%d, giving up”,
self->threadId, thread->threadId);
/* try to get a debuggerd dump from the spinning thread */
=> dvmNukeThread(thread);
/* abort the VM */
dvmAbort();
… …
}
而从堆栈我们看出,杀死进程的时候,我们正调用DexFile.loadDex
,这个方法最后会调用到dvmRawDexFileOpen
里面,执行 write 操作。而这个 write 涉及 I/O 操作,是比较耗时的。所以,当线程在做 dexopt,长时间无法响应虚拟机的挂起请求时,就会触发这个问题。
一般来说,虚拟机在执行 Java 代码的时候,都会是 RUNNING 状态。而只要调用了 JNI 方法,在执行到 C/C++代码的时候,就会切换为 NATIVE 状态。而虚拟机只会在 RUNNING 状态下会挂起线程,如果是在 NATIVE 状态下,虚拟机是不会要求线程必须挂起的。
不过,这里有一个特殊之处。虽然DexFile.loadDex
方法最终也走到了 JNI 里面调用dvmRawDexFileOpen
函数,但由于DexFile
类是虚拟机的内部类,Dalvik 虚拟机不会在内部类执行 JNI 方法的时候将线程切换为 NATIVE 状态,仍然会保持原来的 RUNNING 状态。于是,在 RUNNING 状态下