Java JNA/ByteBuffer未被释放,导致C堆内存不足

Java JNA/ByteBuffer未被释放,导致C堆内存不足,java,c,jna,Java,C,Jna,让我首先说,我对JNA和Java直接本机内存分配的理解充其量是发自内心的,所以我试图描述我对发生了什么的理解。除了回复之外的任何更正都将非常好 我正在运行一个使用JNA混合Java和C本机代码的应用程序,并且正在运行一个可重复的问题,Java垃圾收集器无法释放直接本机内存分配的引用,导致C堆内存不足 我确信我的C应用程序不是分配问题的根源,因为我正在将java.nio.ByteBuffer传递到我的C代码中,修改缓冲区,然后在java函数中访问结果。在每次函数调用期间,我有一个malloc和一个

让我首先说,我对JNA和Java直接本机内存分配的理解充其量是发自内心的,所以我试图描述我对发生了什么的理解。除了回复之外的任何更正都将非常好

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

我确信我的C应用程序不是分配问题的根源,因为我正在将
java.nio.ByteBuffer
传递到我的C代码中,修改缓冲区,然后在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);
}
#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;
}
那么我的C代码可能是这样的:

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);
}
#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;
}
#包括
#包括
void someOp(无符号字符*buf,整数大小){
无符号字符*foo;
foo=malloc(1000000);
如果(!foo){
fprintf(stderr,“malloc 1000000字节内存失败\n”);
返回;
}
免费(foo);
buf[0]=100;
}
问题是在反复调用这个函数之后,Java堆有些稳定(增长缓慢),但C函数最终无法分配更多内存。在较高的层次上,我认为这是因为Java正在为C堆分配内存,但没有清理指向该内存的ByteBuffer,因为Java ByteBuffer对象相对较小

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

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

我对问题的理解是否不正确(是否存在我运行不当的情况)


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

我怀疑您的问题是由于使用了直接字节缓冲区。它们可以在Java堆之外分配

如果您经常调用该方法,并且每次都分配小的缓冲区,那么您的使用模式可能不适合直接缓冲区


为了隔离问题,我将切换到(Java)堆分配的缓冲区(只需使用
allocate
方法来代替
allocateDirect
。如果这使您的内存问题消失,您已经找到了罪魁祸首。下一个问题是直接字节缓冲区在性能方面是否有任何优势。如果没有(我猜不是这样的),那么您就不必担心如何正确地清理它。

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

那么你能做些什么呢?好吧,我要做的第一件事是使用-Xms命令行参数保持初始JVM堆大小较小。如果你的程序不断在Java堆上分配少量内存,这可能会导致问题,因为它会更频繁地运行GC

我还将使用pmap工具(或Windows上的任何等效工具)来检查虚拟内存映射。有可能您正在通过分配可变大小的缓冲区来分割C堆。如果是这样,那么您将看到一个更大的虚拟映射,在“anon”之间有间隙块。解决方案是分配大于您需要的恒定大小的块。

您实际面临的问题。错误报告中列出的最佳解决方法是:

  • “可以使用-XX:MaxDirectMemorySize=选项来限制使用的直接内存量。如果试图分配直接内存而导致超出此限制,则会导致完全GC,从而引发引用处理和释放未引用的缓冲区。”
其他可能的解决办法包括:

  • 偶尔插入显式System.gc()调用,以确保回收直接缓冲区
  • 减少年轻一代的规模,迫使他们更频繁地进行地面军事训练
  • 在应用程序级别显式池直接缓冲区

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

如果堆内存不足,GC会自动触发。但是,如果直接内存不足,则不会触发GC(至少在Sun的JVM上),即使GC会释放足够的内存,也会出现OutOfMemory错误。我发现在这种情况下,您必须手动触发GC


更好的解决方案可能是重用相同的ByteBuffer,这样就不需要重新分配ByteBuffers。

要释放direct
缓冲区的[]内存,可以使用

函数
GetDirectBufferAddress(JNIEnv*env,jobject buf)
[]可用于获取
缓冲区的内存指针,然后在指针上获取标准
释放(void*ptr)
命令以释放内存

您可以使用的是's
Native.getDirectBufferPointer(Buffer)
[],而不是编写C之类的代码从Java调用上述函数

之后剩下的唯一一件事就是放弃对<代码的所有引用