我有一个 JNI 回调:
void callback(Data *data, char *callbackName){
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
/* start useful code*/
/* end useful code */
jvm->DetachCurrentThread();
}
当我这样运行它(空的有用代码)时,我发生了内存泄漏。如果我注释掉整个方法,就没有泄漏。附加/分离线程的正确方法是什么?
我的应用程序处理实时声音数据,因此负责数据处理的线程必须尽快完成,以便为下一批处理做好准备。因此,对于这些回调,我创建了新线程。每秒有数十个甚至数百个,它们将自己附加到 JVM,调用重绘图形的回调函数,分离并死亡。这是做这些事情的正确方法吗?如何处理内存泄漏?
编辑:打字错误
好的,我已经创建了所需的最小代码:
package test;
public class Start
{
public static void main(String[] args) throws InterruptedException{
System.loadLibrary("Debug/JNITest");
start();
}
public static native void start();
}
和
#include <jni.h>
#include <Windows.h>
#include "test_Start.h"
JavaVM *jvm;
DWORD WINAPI attach(__in LPVOID lpParameter);
JNIEXPORT void JNICALL Java_test_Start_start(JNIEnv *env, jclass){
env->GetJavaVM(&jvm);
while(true){
CreateThread(NULL, 0, &(attach), NULL, 0, NULL);
Sleep(10);
}
}
DWORD WINAPI attach(__in LPVOID lpParameter){
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
jvm->DetachCurrentThread();
return 0;
}
当我运行 VisualJM 探查器时,我得到了通常的锯齿模式,那里没有泄漏。堆使用量在 5MB 左右达到峰值。然而,观察进程资源管理器确实显示出一些奇怪的行为:内存缓慢上升,每秒 4K 一分钟左右,然后突然所有这些分配的内存下降。这些下降与垃圾收集不对应(与分析器中的锯齿相比,它们发生的频率较低,释放的内存也较少)。
所以我最好的选择是它是处理数万毫秒生存线程的一些操作系统行为。有大师对此有解释吗?
最佳答案
关于从 native 代码回调到 Java 的几点:
- 只有在 jvm->GetEnv() 返回 JNI_EDETACHED 时才应调用 AttachCurrentThread。如果线程已经附加,这通常是空操作,但您可以节省一些开销。
- 只有在调用 AttachCurrentThread 时才应调用 DetachCurrentThread。
- 如果您希望将来在同一线程上被调用,请避免分离。
根据您的 native 代码的线程行为,您可能希望避免分离,而是存储对所有 native 线程的引用以在终止时处理(如果您甚至需要这样做;您可以依靠应用程序关闭来清理上)。
如果您持续附加和分离 native 线程,VM 必须持续将(通常是相同的)线程与 Java 对象相关联。某些 VM 可能会重复使用线程或临时缓存映射以提高性能,但如果您不依赖 VM 为您做这些,您将获得更好、更可预测的行为。
关于java - JNI 附加/分离线程内存管理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9642506/