多次调用findViewById后,Java / JNI突然崩溃

时间:2013-06-07 08:49:40

标签: java android java-native-interface

#define PRINTF(...) ((void)__android_log_print(ANDROID_LOG_INFO, "yaui", __VA_ARGS__))

jfindViewById = (Env)->GetMethodID(cls, "findViewById", "(I)Landroid/view/View;");
for (int i = 0; i < 1000; i++) {
    PRINTF("%i ", i);
    view = (jobject) (Env)->CallObjectMethod(Obj, jfindViewById, N);
}

循环将执行大约500次,然后程序将崩溃。 我无法理解为什么。必须是内存泄漏或资源泄漏,但这可能泄漏到什么地方?

在现实生活中,我不需要像这样一次执行1000次此功能。这是我在搜索问题时创建的最小循环。

2 个答案:

答案 0 :(得分:2)

显然,您的findViewById方法返回Java对象,并且您正在jobject中存储这些view引用并始终覆盖它们。但是,这对JVM来说是一个问题:垃圾收集如何知道持有jobject对象引用的本机代码?我知道一些JavaScript引擎只是遍历整个本机堆栈并检查是否有任何值是有效的对象引用。脏。 JVM使用不同的方法:当线程进入本机代码时,在JVM中创建本地参考框架。每当本机代码被赋予本地jobject引用时,通过方法参数,通过调用返回对象的方法或通过从字段读取对象值,该引用被添加到该JVM堆栈帧。现在GC可以查看堆栈帧并立即查看正在引用的对象。然后,当本机代码返回时,将删除特殊堆栈帧,因此将释放本地引用。根据最新的JNI文档,JVM默认为该堆栈帧中的 16 本地引用分配空间,但为了保持向后兼容性,它可以分配更多(它可能会调整大小)缓冲或类似的东西)。错误消息表明JVM无法无限地执行此操作(max=512),并且您的1000个引用(每个循环迭代一个)明显超出该限制。

现在你有几个选择:

  • 删除本地引用:DeleteLocalRef方法将释放本地引用,允许您在引用缓冲区中重用空间。
  • 确保本地参考容量:EnsureLocalCapacity验证当前线程是否可以容纳给定数量的引用。它将返回一个负数,如果不是这样,则抛出OutOfMemoryError
  • 创建新的本地参考框架:您可以使用PushLocalFrame创建具有您选择的容量的新本地参考框架。完成后,您可以通过拨打PopLocalFrame一次性释放该框架内的所有引用。

如果可以,您当然应该在不再需要本地参考时删除它们。

答案 1 :(得分:0)

看着

int __android_log_print(int prio, const char *tag, const char *fmt, ...);

所以你的#define应该有两个逻辑参数:一个用于格式字符串,其余的是参数:

#define PRINTF(format, ...) \
  ((void)__android_log_print(ANDROID_LOG_INFO, "yaui", format, __VA_ARGS__))

格式字符串实际上不是varargs的一部分。但从逻辑的角度来看,它用于检测堆栈后面的预期参数的数量。