JavaCPP Leptonica: как очистить память от дескрипторов pixClone

До сих пор я всегда использовал pixDestroy для очистки объектов PIX в моем Приложение JavaCPP/Leptonica. Однако недавно я заметил странную проблему с утечкой памяти, которую я обнаружил в функции 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 (не размер кучи) увеличивается примерно до 1 ГБ и не освобождается до тех пор, пока не закончится спящий режим и не завершится тест.

Глядя на документы pixClone, мы видим, что он фактически создает дескриптор для существующий PIX:

Заметки:

  1. «Клон» — это просто дескриптор (ptr) существующего изображения. Это реализовано, потому что (а) изображения могут быть большими и, следовательно, дорогими для копирования, и (б) дополнительные дескрипторы структуры данных должны быть сделаны с помощью простой политики, чтобы избежать как двойных освобождений, так и утечек памяти. Пикс подсчитывается по ссылкам. Побочным эффектом pixClone() является увеличение счетчика ссылок на 1.

  2. Следует использовать следующий протокол: (a) Всякий раз, когда вам нужен новый дескриптор существующего изображения, вызывайте pixClone(), который просто увеличивает количество ссылок. (b) Всегда вызывайте pixDestroy() для всех дескрипторов. Это уменьшает счетчик ссылок, обнуляет дескриптор и уничтожает пикс только тогда, когда pixDestroy() вызывается для всех дескрипторов.

Если я правильно понимаю, я действительно вызываю pixDestroy для всех дескрипторов, поэтому счетчик ссылок должен достичь нуля и, следовательно, PIX должен быть уничтожен. Хотя ясно, что это не так. Может кто-нибудь сказать мне, что я делаю неправильно? Заранее спасибо!


person SND    schedule 12.11.2019    source источник


Ответы (1)


В качестве оптимизации для общего случая, когда функция возвращает указатель, который она получает в качестве аргумента, JavaCPP также возвращает тот же объект в JVM. Это то, что происходит с pixClone(). Он просто возвращает указатель, который пользователь передает в качестве аргумента, и, таким образом, оба img и clone в конечном итоге ссылаются на один и тот же объект в Java.

Теперь, когда pixDestroy() вызывается для первой ссылки img, Leptonica услужливо сбрасывает свой адрес на 0, но теперь мы потеряли адрес, и второй вызов pixDestroy() получает этот нулевой указатель, что приводит к нулевой операции и утечке памяти.

Одним из простых способов избежать этой проблемы является явное создание новой ссылки PIX после каждого вызова pixClone(), например, в этом случае:

PIX clone = new PIX(pixClone(img));
person Samuel Audet    schedule 12.11.2019