JNI Attach/Detach 线程内存管理

Posted

技术标签:

【中文标题】JNI Attach/Detach 线程内存管理【英文标题】:JNI Attach/Detach thread memory management 【发布时间】:2012-03-09 23:49:00 【问题描述】:

我有一个 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 为您做这些事情,您将获得更好、更可预测的行为。

【讨论】:

我明白你的意思,我通常会同意。我正在创建的线程真的很短暂。我创建它们(使用 WinApi CreateThread),然后立即将它们附加到 JVM。在 Java 中,他们使用新值重新绘制 Swing 图。完成后,它们分离并停止执行(返回 0),此时它们应该被操作系统销毁。它们用于将新值传递给 Java。每个声道每秒大约有 40 个(我最多使用 32 个声道)。 你可以考虑线程池。让您的线程停留更长时间并从队列中获取输入,而不是不断产生新线程。这将减少 OS 和 Java 中的线程管理开销。 如果您在 JNI 中执行任何 Java 操作,而不仅仅是调用一个方法,您可能还希望围绕这些操作推送/弹出一个本地框架。 您在哪里检测到泄漏?在操作系统级别?未收集的 Java 对象? java 之外的本地内存? 进程资源管理器中的内存需求无限攀升。如果我省略附加/分离,它会保持不变。【参考方案2】:

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

【讨论】:

以上是关于JNI Attach/Detach 线程内存管理的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 数据部分更新之Attach/Detach(转)

[Blue Prism自学]Attach, Detach, Read and Actions

从本地方法栈看到jni调用

JNI内存方面说明以及相关类型手动释放内存

Android JNI - 调用AttachCurrentThread而不使用DetachCurrentThread

第八章 JVM内存管理