0

到目前为止,我一直使用pixDestroy来清理我的 JavaCPP/Leptonica 应用程序中的 PIX 对象。然而,我最近注意到一个奇怪的内存泄漏问题,我追踪到 Leptonica 函数在内部返回一个 pixClone 结果。我设法通过使用以下简单测试重现了该问题:

    @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 的句柄:

笔记:

  1. “克隆”只是现有像素的句柄(ptr)。它的实现是因为 (a) 图像可能很大,因此复制成本很高,并且 (b) 需要使用简单的策略对数据结构进行额外的处理,以避免双重释放和内存泄漏。Pix 是引用计数的。pixClone() 的副作用是引用计数增加 1。

  2. 要使用的协议是: (a) 每当您想要一个现有图像的新句柄时,调用 pixClone(),它只会增加一个引用计数。(b) 始终在所有句柄上调用 pixDestroy()。这会减少引用计数,使句柄为空,并且仅在对所有句柄调用 pixDestroy() 时才销毁 pix。

如果我理解正确,我确实在所有句柄上调用 pixDestroy,所以引用计数应该达到零,因此 PIX 应该被销毁。显然,情况并非如此。有人可以告诉我我做错了什么吗?提前致谢!

4

1 回答 1

1

作为对函数返回作为参数接收的指针的常见情况的优化,JavaCPP 也将相同的对象返回给 JVM。这就是正在发生的事情pixClone()。它只是返回用户作为参数传递的指针,因此两者img最终clone都引用了 Java 中的同一个对象。

现在,当pixDestroy()在第一个引用上调用时img,Leptonica 有用地将其地址重置为 0,但我们现在丢失了地址,第二次调用pixDestroy()接收到该空指针,导致无操作和内存泄漏。

避免此问题的一种简单方法是PIX在每次调用 之后显式创建一个新引用pixClone(),例如,在这种情况下:

PIX clone = new PIX(pixClone(img));
于 2019-11-12T13:52:14.790 回答