程序从IP摄像机接收以字节为单位的图像数据,然后处理图像。程序启动的第一次使用470Mb的RAM,并且每1秒增加一个15Mb,它将继续,直到没有足够的空间并且计算机挂起。
方法getImage()
每100毫秒调用一次
我做了一些实验,在这里分享。原始代码是这样的:(其中缓冲区只创建一次,之后可以重用)
private static final int WIDTH = 640;
private static final int HEIGHT = 480;
private byte[] sJpegPicBuffer = new byte[WIDTH * HEIGHT];
private Mat readImage() throws Exception {
boolean isGetSuccess = camera.getImage(lUserID, sJpegPicBuffer, WIDTH * HEIGHT);
if (isGetSuccess) {
return Imgcodecs.imdecode(new MatOfByte(sJpegPicBuffer), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
}
return null;
}
在上面的代码中,RAM上升到计算机挂起(99%10Gb)。然后我改变了这样的代码:(在每个循环中它将创建一个新的缓冲区)
private static final int WIDTH = 640;
private static final int HEIGHT = 480;
private Mat readImage() throws Exception {
byte[] sJpegPicBuffer = new byte[WIDTH * HEIGHT];
boolean isGetSuccess = camera.getImage(lUserID, sJpegPicBuffer, WIDTH * HEIGHT);
if (isGetSuccess) {
return Imgcodecs.imdecode(new MatOfByte(sJpegPicBuffer), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
}
return null;
}
在上面的代码中,RAM上升到大约43%(5Gb),然后释放。
现在问题是在第一块代码中似乎是优化的,缓冲区可以重用,避免在每次调用时创建新的内存空间,但结果不是我们想要的。的为什么吗
在第二个代码块中,似乎代码没有第一个代码那么优化,但是效果比第一个代码好。
但一般来说为什么RAM在第一种情况下增加到10Gb而在第二种情况下增加到5Gb。我们怎样才能控制这种情况?
答案 0 :(得分:1)
这是一种推测,虽然我在实际现场几次都看到了类似的情况。
您的Java代码正在与本机相机SDK(dll)进行交互。本机代码类似于在非JVM内存中分配缓冲区,并使用一些内部Java对象来访问该缓冲区。常见(非常糟糕)的做法是在Java对象终结器上中继释放本机缓冲区(如果它不再使用)。
终结器依靠垃圾收集器来触发它们,这就是模式经常失败的原因。虽然终结器最终可以保证最终运行,但实际上只要Java堆中有足够的空间并且本地内存不能及时解除分配,就不会发生这种情况。
Java堆大小有硬限制,但只要操作系统允许它增长,C / C ++使用的本机内存池就会增长。
关于您的问题
我假设在您的第一个片段中,Java堆流量很低。 GC处于空闲状态,并且没有执行终结器,因此在Java堆外部分配的内存不断增长。
在第二个片段中,您正在对Java堆施加压力,迫使GC频繁运行。执行GC终结器的副作用并释放本机内存。
代替在本机代码中分配的终结器和缓冲区,您的相机SDK可以在Java直接内存缓冲区上中继(这些内存直接访问C代码,因此可以方便地通过JVM边界传递数据)。虽然效果大致相同,因为Java直接缓冲区实现使用相同的模式(使用幻像引用而不是终结器)。
<强>建议强>
-XX:+PrintGCDetails
和-XX:+PrintReferenceGC
选项会打印有关参考处理的信息,因此您可以验证是否确实使用了终结器/幻像引用。-XX:MaxDirectMemorySize=X
可用于限制直接缓冲区使用,如果您的camara SDK继续使用它们。虽然它不是一个解决方案,但是在操作系统内存耗尽之前让你的应用程序OOM耗尽的安全网System.gc()
)。这是另一个糟糕的选择,因为System.gc()
的行为依赖于JVM。 PS
This is my post关于使用终结器和幻像引用的资源管理。