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

2022-09-03 12:47:02

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

因此,我最好的选择是,它是一些操作系统行为,处理数万个毫秒级线程。一些上师对此有解释吗?


答案 1

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

  • 仅当 jvm->GetEnv() 返回JNI_EDETACHED时,才应调用 AttachCurrentThread。如果线程已经附加,这通常是无操作的,但您可以节省一些开销。
  • 仅当调用 AttachCurrentThread 时,才应调用 DetachCurrentThread。
  • 如果希望将来在同一线程上被调用,请避免分离。

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

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


答案 2

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


推荐