Утечка памяти из итерирующих кадров Opencv

Я использую оболочку Java OpenCV. Я попытался написать Итератор над кадрами фильма. Моя проблема в том, что итератор - это огромная утечка памяти. Вот очень упрощенная версия итератора, которая имеет эту утечку:

public static final class SimpleIt implements Iterator<Mat> {

    private final VideoCapture capture;
    boolean hasNext;

    public SimpleIt(final VideoCapture capture) {
        this.capture = capture;
        hasNext = capture.grab();
    }

    @Override
    public boolean hasNext() {
        return hasNext;
    }

    @Override
    public Mat next() {
        final Mat mat = new Mat();
        capture.retrieve(mat);
        hasNext = capture.grab();
        return mat;
    }
}

I Перебираем этот код с помощью этого цикла:

    final VideoCapture vc = new VideoCapture("/path/to/file");
    final SimpleIt it = new SimpleIt(vc);
    while (it.hasNext) {
        it.next();
    }

Простое повторение приведет к увеличению потребления памяти линейным. Я вижу, что проблема заключается в первой строке в следующем() - методе. Он всегда создает новый Mat. Но, говоря только о java, этот Mat выйдет из области действия, как только итерационный код перейдет к следующему изображению.

Я мог бы преодолеть эту проблему, не используя новый Mat каждый раз, но переписывая всегда один и тот же Mat-Object, например:

    private final VideoCapture capture;
    private final Mat mat = new Mat();
    boolean hasNext;

    @Override
    public Mat next() {
        capture.retrieve(mat);
        hasNext = capture.grab();
        return mat;
    }

Но теперь последний кадр, который был задан итератором, будет перезаписан. Таким образом, я не могу держать его снаружи для последующего использования, если меня интересует этот единственный кадр. Конечно, я мог бы его скопировать, но это тоже было бы дорого.

Я предполагаю, что проблема в том, что сборщик мусора не будет уничтожать объекты Mat, потому что он не распознает потребление памяти, поскольку это не пустое пространство Java. Вызов mat.release() в цикле поможет, но, конечно, в реальном коде это означает, что у меня не будет сбора мусора для объектов Mat.

У кого-нибудь есть идея, как это сделать?

Edit:

Поскольку кажется, что не ясно, что проблема с моим вторым решением, я записываю его более явно. Подумайте о следующем коде, используя итератор:

    final VideoCapture vc = new VideoCapture("/path/to/file");
    final SimpleIt it = new SimpleIt(vc);
    int i = 0;
    Mat save = null;
    while (it.hasNext) {
        final Mat next = it.next();
        if (i == 10) {
            save = next;
            Highgui.imwrite("/path/to/10.png", save);
        } else if (i == 30) {
            Highgui.imwrite("/path/to/30.png", save);
        }
        i++;
    }

Со второй версией итератора 10.png и 30.png будут разные изображения. Но это явно не то, что предполагалось.

Ответ 1

Вы действительно должны называть mat.release().

У меня очень похожая проблема с твоей в моем приложении. Частота кадров была настолько высокой, что куча java выросла до общей доступной системной памяти, что иногда приводило к сбою JVM. GC был слишком медленным, и у меня не было механизма проверки доступной памяти и ждать, если этого недостаточно.

Одним из решений было уменьшить частоту кадров, просто используя Thread.sleep(), который, конечно, не был приемлемым. Но это помогло GC выполнить эту работу вовремя.

Наконец, с помощью mat.release() исправлена ​​проблема.

Вам не нужно беспокоиться о сборке мусора объектов Mat, потому что этот вызов освобождает только базовые данные. Оболочка объектов Java будет удалена GC, когда наступит подходящее время.

Ответ 2

Я просто хочу добавить свои $0.02, когда я столкнулся с этой проблемой при написании приложения, которое будет работать долгое время.

Mat.release() вызывается автоматически, когда Java Mat-wrapper собирает мусор. Однако, поскольку оболочка Java очень мала по сравнению с изначально выделенным объектом, она может не быть собранной с мусором достаточно быстро.

Следовательно, вы можете либо сделать Mat.release(), когда знаете, что выполняете объект или вызываете System.gc() с регулярными интервалами, чтобы принудительно удалить неиспользуемые объекты.

Ответ 3

Кажется, нет хорошего решения. Я экспериментировал с ним сейчас в течение нескольких часов. Лучшее, что я придумал, регулярно называет сборщик мусора следующим образом:

    int count = 0;

    @Override
    public Mat next() {
        final Mat result = mat;
        mat = new Mat();
        capture.retrieve(mat);
        hasNext = capture.grab();
        if (++count % 200 == 0) {
            System.gc();
        }
        return result;

Так как это работает, это указывает на то, что мое предположение было правильным, java не распознает ОЗУ, выделенную из C, и, таким образом, не вызывает GC, даже если операционная память машины заканчивается.

Это не очень хорошее решение, потому что оно может быть не очень стабильным. Если кто-то другой имеет лучшую идею, мне интересно.

Ответ 4

System.gc(); не работает для меня.

Я добавляю строку:

System.runFinalization();

A Codesnippet:

    startGC--;
    if (startGC==0) {
        System.gc();
        System.runFinalization();
        startGC=100;
    }

Ответ 5

Я бы изменил ваш метод .hasNext на:

public boolean hasNext() {
    return hasNext;
}

И затем описанный ниже метод, который будет скопирован ниже, будет работать нормально... Вы будете выполнять итерацию до тех пор, пока ничего не останется, после чего вы можете назначить последнее изображение новому объекту Mat...

public Mat next() {
    capture.retrieve(mat);
    hasNext = capture.grab();
    return mat;
}

а затем:

final VideoCapture vc = new VideoCapture("/path/to/file");
final SimpleIt it = new SimpleIt(vc);
final Mat lastFrame = new Mat();
while (it.hasNext) {
    lastFrame = it.next();
}

Я понимаю, что это создает дополнительное использование памяти. Вероятно, есть способ обойти это, но он должен хорошо работать...