JNI附加/分离线程内存管理

时间:2012-03-09 23:49:00

标签: java java-native-interface

我有一个JNI回调:

void callback(Data *data, char *callbackName){
    JNIEnv *env;
    jvm->AttachCurrentThread((void **)&env, NULL);
    /* start useful code*/

    /* end useful code */
    jvm->DetachCurrentThread();
}

当我像这样运行它(空的有用代码)时,我得到了内存泄漏。如果我注释掉整个方法,就没有泄漏。附加/分离线程的正确方法是什么?

我的应用程序处理实时声音数据,因此必须尽快完成负责数据处理的线程,以便为另一批次做好准备。所以对于这些回调,我创建了新的线程。它们每秒都有几十甚至几百个,它们将自己附加到JVM,调用一个重新绘制图形,分离和死亡的回调函数。这是做这件事的正确方法吗?如何处理泄漏的内存?

编辑:错字

好的,我已经创建了一个所需的mimimal代码:

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一秒钟一分钟左右然后突然所有这些分配的内存下降。这些下落与垃圾收集不相符(它们发生频率较低,并且比分析器中的锯齿更少释放内存)。

所以我最好的选择是处理成千上万个生命线程的操作系统行为。有些大师对此有解释吗?

2 个答案:

答案 0 :(得分:15)

关于从本机代码回调到Java的几点:

  • 如果jvm-&gt; GetEnv()返回零值,则只应调用AttachCurrentThread。如果线程已经连接,它通常是一个无操作,但你可以节省一些开销。
  • 只有在调用AttachCurrentThread时才会调用DetachCurrentThread。
  • 如果您希望将来在同一个线程上调用,请避免分离。

根据您的本机代码的线程行为,您可能希望避免分离,而是存储对所有本机线程的引用以便在终止时进行处理(如果您甚至需要这样做;您可以依赖应用程序关闭来清理向上)。

如果您不断附加和分离本机线程,则VM必须不断地将(通常是相同的)线程与Java对象相关联。有些虚拟机可能会重新使用线程,或临时缓存映射以提高性能,但如果您不依赖虚拟机为您执行此操作,您将获得更好,更可预测的行为。

答案 1 :(得分:7)

我想出了问题。它是悬挂在我没有销毁的JNI代码中的本地引用。每个回调都会创建一个新的本地引用,从而导致内存泄漏。当我将本地引用转换为全局引用时,我可以重用它,问题就消失了。