概述
有幸碰到过这样一个错误:JNI ERROR (app bug): local reference table overflow (max=512)
错误的意思是JVM的局部引用表溢出了。局部引用表的作用是在Native方法返回前,保存局部引用,一旦其返回,则将局部引用表中的对象回收。简单理解,即为垃圾回收服务的。
如何获取一个局部引用呢?有两种方法:
1. 通过NewLocalRef
创建一个局部引用;
2. 引用一个Java对象,如引用CallStaticObjectMethod
返回的对象
如何释放呢?有两种方法:
1. 通过DeleteLocalRef
2. 等Native方法返回
重现
要重现这个问题,很简单,在Native方法中NewLocalRef 512次即可。
常见的Native方法调用,调用方向是Java -> c/c++ -> Java
,也就是Java代码调用了一个Native方法,做了某件事,然后返回Java继续执行。这一调用过程局部引用表将在Native调用结束时会自动回收。
然而,要特别注意的是另一种形式的Native方法。也是最容易出现local reference table overflow
的。调用方向是Java -> c/c++ -> thread -> callback Java
,也就是Native方法起了一个线程,后台做某件事情,但又需要Java方法提供一些信息。
注意callback Java,这里如果是CallStaticObjectMethod
之类的方法,将引用Java对象,占用一格局部引用表。但什么时候Native方法返回,并回收局部引用表呢?要到线程执行结束,或者说DetachCurrentThread之后。而一般后台线程的运行时间较长,不断地引用Java对象而没有及时回收,很容易触发该bug。
解决
最可靠的解决方法是及时回收(即DeleteLocalRef
)
对于回调Java方法的后台线程,另一解决方法是,仅在必要时attach线程,并及时detach线程
参考
以下是来自Android developer网站的解释
One unusual case deserves separate mention. If you attach a native thread with AttachCurrentThread, the code you are running will never automatically free local references until the thread detaches. Any local references you create will have to be deleted manually. In general, any native code that creates local references in a loop probably needs to do some manual deletion