直到现在,我一直使用pixDestroy来清理JavaCPP / Leptonica应用程序中的PIX对象。但是,我最近注意到了一个奇怪的内存泄漏问题,该问题一直追溯到内部返回pixClone结果的Leptonica函数。我设法通过使用以下简单测试来重现该问题:
@Test
public void test() throws InterruptedException {
String pathImg = "...";
for (int i = 0; i < 100; i++) {
PIX img = pixRead(pathImg);
PIX clone = pixClone(img);
pixDestroy(clone);
pixDestroy(img);
}
Thread.sleep(10000);
}
到达Thread.sleep时,Windows任务管理器中的RAM内存使用量(不是堆大小)已增加到大约1GB,并且直到睡眠结束并且测试完成才释放。
看看pixClone的文档,我们看到它实际上为现有PIX创建了一个句柄:
注意:
“克隆”只是现有像素的句柄(ptr)。这样做是因为(a)图像可能很大,因此图像昂贵 复制,以及(b)数据结构的额外句柄需要使用 避免双重释放和内存泄漏的简单策略。像素是 参考计数。 pixClone()的副作用增加了1 在引用计数中。
要使用的协议是:(a)每当您想要现有图像的新句柄时,请调用pixClone(),它只会增加引用计数。 (b) 始终在所有句柄上调用pixDestroy()。这会减少参考 count,使句柄为空,并且仅在pixDestroy()时销毁pix 已在所有句柄上被调用。
如果我正确理解这一点,那么我确实在所有句柄上都调用pixDestroy,因此引用计数应该达到零,因此PIX应该已经被销毁。显然,事实并非如此。有人可以告诉我我在做什么错吗?预先感谢!
答案 0 :(得分:1)
作为对函数返回作为参数接收的指针的常见情况的优化,JavaCPP还将相同的对象返回给JVM。 pixClone()
就是这种情况。它只是返回用户作为参数传递的指针,因此img
和clone
最终都引用Java中的同一对象。
现在,当在第一个引用pixDestroy()
上调用img
时,Leptonica会很有帮助地将其地址重置为0,但现在我们丢失了地址,而第二次调用了pixDestroy()
接收到该空指针,从而导致noop和内存泄漏。
避免此问题的一种简单方法是在每次调用PIX
之后显式创建一个新的pixClone()
引用,例如,在这种情况下:
PIX clone = new PIX(pixClone(img));