首先我要说的是,我对 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);
}

那么我的 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;
}

问题是,重复调用该函数后,Java 堆有些稳定(增长缓慢),但 C 函数最终无法分配更多内存。从较高的层面来看,我认为这是因为 Java 正在向 C 堆分配内存,但没有清理指向该内存的 ByteBuffer,因为 Java ByteBuffer 对象相对较小。

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

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

我对问题的理解是否不正确(是否有我运行不当的地方)?

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

有帮助吗?

解决方案

我想你已经正确诊断:你不会耗尽Java堆,所以JVM不垃圾收集和映射缓冲区不释放。你没有运行GC时的问题,这一事实手工似乎证实了这一点。你也可以打开详细日志记录收集的二次确认。

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

我还使用 PMAP 工具(或任何其等同物是在Windows上)来检查虚拟存储器映射。这有可能是你正在分段C堆中,通过分配可变大小的缓冲区。如果是这样的话,那么你会看到每一个更大的虚拟地图,以“匿名”块之间的间隙。并将该溶液有分配恒定大小的块是比需要大。

其他提示

你实际上是面对 知中的错误Java VM.最好的解决办法中列出的错误报告的是:

  • "-XX:MaxDirectMemorySize=选项可以被用来限制数量的直接使用的存储器。尝试分配直接内存就会导致这种限制超过了引起一个完整的GC以便挑起参照处理和释放的未引用的缓冲器。"

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

  • 插入偶有明确系统。gc()调用,以确保直接缓冲区被开垦。
  • 减少尺寸的年轻一代的力量更加频繁的Gc.
  • 明确游泳池直接缓冲区在应用水平。

如果你真的想要依赖于直接字节的缓冲区,然后我建议集中在应用水平。根据复杂的程序,你甚至可能会只是缓和重复使用相同的缓冲器(小心的多线程).

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

如果您经常调用该方法,每一次分配小缓冲区,您使用模式可能不是一个很好的适合直接缓冲区。

为了找出问题,我会切换到(Java)的堆分配的缓冲区(只使用到位allocateallocateDirect方法。如果让你的内存问题消失,你已经找到了罪魁祸首。接下来的问题是是否的直接的字节的缓冲区有什么优势表现,明智的。如果没有(我猜想,事实并非如此),那么你就不用担心如何清洗它正确。

如果您运行的堆内存,一个GC被自动触发。但是,如果您运行的直接内存,GC没有被触发(Sun的JVM至少),你只得到一个OutOfMemoryError即使GC会释放足够的内存。我发现,你必须在这种情况下手动触发GC。

一个更好的解决方案可以是重复使用相同的ByteBuffer所以你永远需要重新acllocate的ByteBuffers。

免费直达 Buffer[1] 内存,你可以使用 JNI.

功能 GetDirectBufferAddress(JNIEnv* env, jobject buf)[3]JNI 6 API 可用于获取指向内存的指针 Buffer 然后是标准 free(void *ptr) 指针上的命令释放内存。

您可以使用 C 等代码来从 Java 调用上述函数,而不是编写 JNANative.getDirectBufferPointer(Buffer)[6]

之后唯一剩下的就是放弃所有对 Buffer 目的。Java 的垃圾回收将释放 Buffer 实例与任何其他非引用对象一样。

请注意直接 Buffer 不一定 1:1 映射到已分配的内存区域。例如 JNI API 有 NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity)[7]. 。因此,您应该只释放以下内存 Buffer的,您知道其内存分配区域与本机内存是一对一的。

我也不知道能不能直接免费 Buffer 由Java创建的 ByteBuffer.allocateDirect(int)[8] 与上面完全相同的原因。它可能是 JVM 或 Java 平台实现的特定细节,它们是否使用池或在分发新的直接内存时执行 1:1 内存分配 Buffers。

以下是我的库中有关直接的稍微修改过的片段 ByteBuffer[9] 处理(使用 JNA Native[10]Pointer[11] 类):

/**
 * Allocate native memory and associate direct {@link ByteBuffer} with it.
 * 
 * @param bytes - How many bytes of memory to allocate for the buffer
 * @return The created {@link ByteBuffer}.
 */
public static ByteBuffer allocateByteBuffer(int bytes) {
        long lPtr = Native.malloc(bytes);
        if (lPtr == 0) throw new Error(
            "Failed to allocate direct byte buffer memory");
        return Native.getDirectByteBuffer(lPtr, bytes);
}

/**
 * Free native memory inside {@link Buffer}.
 * <p>
 * Use only buffers whose memory region you know to match one to one
 * with that of the underlying allocated memory region.
 * 
 * @param buffer - Buffer whose native memory is to be freed.
 * The class instance will remain. Don't use it anymore.
 */
public static void freeNativeBufferMemory(Buffer buffer) {
        buffer.clear();
        Pointer javaPointer = Native.getDirectBufferPointer(buffer);
        long lPtr = Pointer.nativeValue(javaPointer);
        Native.free(lPtr);
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top