JNA/ByteBuffer没有被释放并导致C堆耗尽内存

Mar*_*iot 11 c java jna

首先我要说的是,我对JNA和Java如何指导本机内存分配的理解充其量只是内心,因此我试图描述我对正在发生的事情的理解.除了回复之外的任何更正都会很棒......

我正在运行一个使用JNA混合Java和C本机代码的应用程序,并且正在运行Java垃圾收集器无法释放对直接本机内存分配的引用的可重现问题,从而导致C堆内存不足.

我很肯定我的C应用程序不是分配问题的根源,因为我正在传入一个java.nio.ByteBufferC代码,修改缓冲区,然后在我的Java函数中访问结果.在每次函数调用期间,我都有一个单独malloc的对应free,但在Java中反复运行代码后,malloc最终会失败.

这是一个有点夸张的代码集,展示了这个问题 - 实际上我试图在函数调用期间在C堆上分配大约16-32MB.

我的Java代码做了类似的事情:

public class MyClass{
    public void myfunction(){
        ByteBuffer foo = ByteBuffer.allocateDirect(1000000);
        MyDirectAccessLib.someOp(foo, 1000000);
        System.out.println(foo.get(0));
    }
}

public MyDirectAccessLib{
    static {
        Native.register("libsomelibrary");
    }
    public static native void someOp(ByteBuffer buf, int size);
}
Run Code Online (Sandbox Code Playgroud)

然后我的C代码可能是这样的:

#include <stdio.h>
#include <stdlib.h>
void someOp(unsigned char* buf, int size){
    unsigned char *foo;
    foo = malloc(1000000);
    if(!foo){
        fprintf(stderr, "Failed to malloc 1000000 bytes of memory\n");
        return;
    }
    free(foo);

    buf[0] = 100;
}
Run Code Online (Sandbox Code Playgroud)

麻烦是在重复调用此函数后,Java堆有点稳定(它增长缓慢),但C函数最终无法分配更多内存.在高层次上我认为这是因为Java正在为C堆分配内存,但是没有清理指向此内存的ByteBuffer,因为Java ByteBuffer对象相对较小.

到目前为止,我发现在我的函数中手动运行GC将提供所需的清理,但这似乎是一个糟糕的想法和一个糟糕的解决方案.

如何更好地管理这个问题,以便适当地释放ByteBuffer空间并控制我的C堆空间?

我对这个问题的理解是不正确的(有什么东西我运行不正常)?

编辑:调整缓冲区大小以更能反映我的实际应用程序,我正在分配大约3000x2000的图像...

Gre*_*osz 8

您实际上面临Java VM中的已知错误.错误报告中列出的最佳解决方法是:

  • "-XX:MaxDirectMemorySize =选项可用于限制使用的直接内存量.尝试分配会导致超出此限制的直接内存会导致完整的GC,从而引发参考处理和释放未引用的缓冲区. "

其他可能的解决方法包括:

  • 插入偶然的显式System.gc()调用以确保回收直接缓冲区.
  • 减少年轻一代的规模以迫使更频繁的GC.
  • 在应用程序级别显式池化直接缓冲区.

如果你真的想依赖直接字节缓冲区,那么我建议在应用程序级别进行池化.根据应用程序的复杂程度,您甚至可以简单地缓存和重用相同的缓冲区(注意多个线程).


kdg*_*ory 4

我认为您已经正确诊断:您永远不会用完 Java 堆,因此 JVM 不会进行垃圾收集,并且不会释放映射的缓冲区。手动运行 GC 时没有出现问题的事实似乎证实了这一点。您还可以打开详细收集日志记录作为辅助确认。

所以,你可以做什么?好吧,我首先尝试的是使用 -Xms 命令行参数来保持初始 JVM 堆大小较小。如果您的程序不断在 Java 堆上分配少量内存,这可能会导致问题,因为它将更频繁地运行 GC。

我还会使用pmap工具(或 Windows 上的任何等效工具)来检查虚拟内存映射。您可能通过分配可变大小的缓冲区来对 C 堆进行碎片化。如果是这种情况,那么您将看到一个更大的虚拟地图,“匿名”块之间有间隙。解决方案是分配比您需要的更大的恒定大小的块。