JNI 代码中的内存泄漏

Posted

技术标签:

【中文标题】JNI 代码中的内存泄漏【英文标题】:Memory leak in JNI code 【发布时间】:2017-04-27 06:52:11 【问题描述】:

下面是在 C 和 Java 进程之间桥接 Linux MQ 的 JNI 代码。虽然我已经发布了所有的 ArrayElements,但是 top 命令的 VIRT 仍然显示出巨大的价值。最大堆大小设置为 2GB,但顶部显示 VIRT 在执行 100 小时后为 10GB。对我来说,这看起来像是内存泄漏,但是,我无法弄清楚 JNI 代码的哪一部分导致了问题。如果有人可以在这方面帮助我,那就太好了。谢谢。

我的 JDK 版本是 1.8.0_91

这是我写的mq_receive方法

JNIEXPORT int JNICALL Java_test_ipc_impl_LinuxMessageQueue_mq_1receive(
        JNIEnv *env, jobject self, jint mqdes, jbyteArray buffer, jint msglen) 
    jbyte* buf = (*env)->GetByteArrayElements(env, buffer, NULL);
    if ((*env)->ExceptionCheck(env))
        return -1;

    struct timespec timeout;

    clock_gettime(CLOCK_REALTIME, &timeout);
    timeout.tv_sec += 1;

    int size = mq_timedreceive(mqdes, (char*) buf, msglen, 0, &timeout);

    if (size == -1) 
        if (errno == ETIMEDOUT) 
            size = 0;
         else 
            perror("mq_receive fail");
        
     else 
        (*env)->SetByteArrayRegion(env, buffer, 0, size, buf);
        if ((*env)->ExceptionCheck(env))
            return -1;
    

    (*env)->ReleaseByteArrayElements(env, buffer, buf, JNI_COMMIT);
    if ((*env)->ExceptionCheck(env))
        return -1;

    return size;

而且,这是我写的 mq_send 方法

JNIEXPORT void JNICALL Java_test_ipc_impl_LinuxMessageQueue_mq_1send(
        JNIEnv *env, jobject self, jint mqdes, jbyteArray buffer, jint msglen) 
    jbyte* buf = (*env)->GetByteArrayElements(env, buffer, NULL);
    if ((*env)->ExceptionCheck(env))
        return;

    if (mq_send(mqdes, (char*) buf, msglen, 0) == -1) 
        perror("mq_send fail");
    
    (*env)->ReleaseByteArrayElements(env, buffer, buf, JNI_COMMIT);
    if ((*env)->ExceptionCheck(env))
        return;


【问题讨论】:

【参考方案1】:

Here's the meaning of the flags 作为最后一个参数传递给ReleaseByteArrayElements

模式标志的可能设置是:

0 更新 Java 堆上的数据。释放副本使用的空间。

JNI_COMMIT 更新 Java 堆上的数据。不要释放副本使用的空间。

JNI_ABORT 不要更新 Java 堆上的数据。释放副本使用的空间。

“0”模式标志是 Release 调用最安全的选择。无论数据的副本是否发生变化,堆都随着副本更新,并且没有泄漏。

因此,在您的 mq_receive 函数中,调用 ReleaseByteArrayElements 并传递 0 作为最终参数。您不需要SetByteArrayRegion 调用,因为数据将由ReleaseByteArrayElements 复制回来。

在您的 mq_send 函数中,您可以传递 JNI_ABORT,因为您没有写入数组。

这应该在两种情况下都释放缓冲区。

以上假设缓冲区是副本而不是固定引用。我认为这是一个副本,否则不会有泄漏。您可以通过将&isCopy 参数传递给GetByteArrayElements 来找出答案。

【讨论】:

非常感谢。会试试看tmr

以上是关于JNI 代码中的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

解析Java的JNI编程中的对象引用与内存泄漏问题

如何在使用 JNI 的 C++/Java 项目中使用 CRT 中的工具检测内存泄漏?

Java 性能调优、JNI 内存泄漏

本机 C 和 Fortran 代码的 Java 内存泄漏

内存泄漏总结

如何检查 C++ 代码中的内存泄漏。有没有检查内存泄漏的免费工具[重复]