基本问题是 Dalvik 是一个安全点挂起 VM,并使用“停止世界”垃圾收集。这意味着,要让 GC 运行,它必须等待所有线程到达可以确定它们不会改变堆的点。
由于某种原因,您的一个线程没有响应 GC 线程的挂起请求。它实际上并没有在本机代码中执行;如果是,线程将处于NATIVE 状态,这被认为是安全的。 (所有对本机堆的访问都通过 JNI 调用进行门控,并且所有 JNI 调用都会进行挂起检查。)
出于性能原因,JIT 能够以跳过挂起检查的方式将已编译代码块链接在一起。如果一个线程挂起的时间过长,挂起的线程将“解链”这些块,并等待更长的时间。最终它开始抱怨,并最终放弃并中止虚拟机。
某些设备使用vendor-modified version of Dalvik 会出错,并且在紧密循环中可能会发生中止。在这种情况下,我不希望在堆栈顶部看到本机方法。
你最好的调试方法是在它不满意的地方附加 gdb 并尝试找出目标线程在做什么。有可能本机代码以某种方式破坏了 VM 状态或返回堆栈,等等它从本机代码返回时线程被卡住了。
编辑后更新:dvmPopFrame() 函数用于从托管堆栈弹出堆栈帧。当 VM 调用您的本机方法时,它会插入一个“中断”帧,以便在展开堆栈以进行异常处理时,VM 不会吹过调用站点。 (它也用于 VM 发出的托管代码方法调用,例如用于反射或<clinit>。)消息PopFrame missed the break 表示未找到中断帧。
中断帧有一个空方法指针。展开堆栈时,dvmPopFrame() 只要看到一个非空方法指针(意味着它不是中断帧)和一个非空前一帧指针(意味着您还没有到达堆栈顶部),就会继续.如果你到达栈顶,你就错过了中断——所有的 Dalvik 栈都以一个真实的方法开始(如果线程通过 JNI 连接到 VM,有时是一个“假”的方法)。
所以我的猜测是本机代码破坏了堆栈,使前一帧指针为空。解决这个问题的一种技术是让 VM 调用一个调用实际本地方法的本地方法。 “中间人”在堆栈上分配一些东西,将其设置为已知值,调用实际方法,然后在返回之前验证其堆栈分配是否未更改。
(可能需要使用这些值来防止编译器优化它们;如果你使用类似的东西:
if (jniEnv == NULL) {
printf("my stuff is ...", ...);
}
那么它永远不会真正运行,因为JNIEnv* 永远不会为空......但编译器不知道这一点。)
有关 Dalvik 堆栈布局的完整说明,请参阅 dalvik/vm/interp/Stack.h。
从本机代码返回时,线程在RUNNABLE 中是正常的。您的本机方法仍在顶部,因为弹出它的代码失败并中止了 VM。