首先我要说的是,我对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的图像...
您实际上面临Java VM中的已知错误.错误报告中列出的最佳解决方法是:
其他可能的解决方法包括:
如果你真的想依赖直接字节缓冲区,那么我建议在应用程序级别进行池化.根据应用程序的复杂程度,您甚至可以简单地缓存和重用相同的缓冲区(注意多个线程).
我认为您已经正确诊断:您永远不会用完 Java 堆,因此 JVM 不会进行垃圾收集,并且不会释放映射的缓冲区。手动运行 GC 时没有出现问题的事实似乎证实了这一点。您还可以打开详细收集日志记录作为辅助确认。
所以,你可以做什么?好吧,我首先尝试的是使用 -Xms 命令行参数来保持初始 JVM 堆大小较小。如果您的程序不断在 Java 堆上分配少量内存,这可能会导致问题,因为它将更频繁地运行 GC。
我还会使用pmap工具(或 Windows 上的任何等效工具)来检查虚拟内存映射。您可能通过分配可变大小的缓冲区来对 C 堆进行碎片化。如果是这种情况,那么您将看到一个更大的虚拟地图,“匿名”块之间有间隙。解决方案是分配比您需要的更大的恒定大小的块。
| 归档时间: |
|
| 查看次数: |
9107 次 |
| 最近记录: |