Подтвердить что ты не робот

Массивное кровоизлияние в память, которое приводит к увеличению размера кучи примерно с 64 МБ, до 1,5 гб примерно за 8 секунд. Проблема с сборщиком мусора?

Здесь проблема:

the memory usage balloons out of control

Как вы можете видеть, воздушные шары использования памяти вышли из-под контроля! Я должен был добавить аргументы в JVM, чтобы увеличить кучу, чтобы избежать ошибок в памяти, пока я выясняю, что происходит. Нехорошо!

Основная сводка приложений (для контекста)

Это приложение (в конечном счете) будет использоваться для базовых на экране CV и шаблонов, соответствующих типам вещей для автоматизации. Я хочу достичь максимально возможной частоты кадров для просмотра экрана и обрабатывать всю обработку с помощью серии отдельных потребительских потоков.

Я быстро обнаружил, что класс Robot на самом деле ужасно быстрый, поэтому я открыл источник, вынул все дублированные усилия и потратил впустую накладные расходы и перестроил его как свой собственный класс под названием FastRobot.

Код класса:

public class FastRobot {
    private Rectangle screenRect;
    private GraphicsDevice screen;
    private final Toolkit toolkit;
    private final Robot elRoboto;
    private final RobotPeer peer;
    private final Point gdloc;
    private final DirectColorModel screenCapCM;
    private final int[] bandmasks;

    public FastRobot() throws HeadlessException, AWTException {
        this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
        this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        toolkit = Toolkit.getDefaultToolkit();
        elRoboto = new Robot();
        peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);

        gdloc = screen.getDefaultConfiguration().getBounds().getLocation();
        this.screenRect.translate(gdloc.x, gdloc.y);

        screenCapCM = new DirectColorModel(24,
                /* red mask */    0x00FF0000,
                /* green mask */  0x0000FF00,
                /* blue mask */   0x000000FF);
        bandmasks = new int[3];
        bandmasks[0] = screenCapCM.getRedMask();
        bandmasks[1] = screenCapCM.getGreenMask();
        bandmasks[2] = screenCapCM.getBlueMask();

        Toolkit.getDefaultToolkit().sync();
    }

    public void autoResetGraphicsEnv() {
        this.screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
        this.screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    }

    public void manuallySetGraphicsEnv(Rectangle screenRect, GraphicsDevice screen) {
        this.screenRect = screenRect;
        this.screen = screen;
    }


    public BufferedImage createBufferedScreenCapture(int pixels[]) throws HeadlessException, AWTException {
//      BufferedImage image;
        DataBufferInt buffer;
        WritableRaster raster;

        pixels = peer.getRGBPixels(screenRect);
        buffer = new DataBufferInt(pixels, pixels.length);

        raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
        return new BufferedImage(screenCapCM, raster, false, null);
    }

    public int[] createArrayScreenCapture() throws HeadlessException, AWTException {
            return peer.getRGBPixels(screenRect);
    }

    public WritableRaster createRasterScreenCapture(int pixels[]) throws HeadlessException, AWTException {
    //  BufferedImage image;
        DataBufferInt buffer;
        WritableRaster raster;

        pixels = peer.getRGBPixels(screenRect);
        buffer = new DataBufferInt(pixels, pixels.length);

        raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
    //  SunWritableRaster.makeTrackable(buffer);
        return raster;
    }
}

В сущности, все, что я изменил с оригинала, перемещает многие из распределений из тел функций и устанавливает их как атрибуты класса, чтобы они не вызывались каждый раз. Это фактически оказало значительное влияние на частоту кадров. Даже на моем жестком ноутбуке с питанием он переходил от ~ 4 кадр/с с классом Robot до ~ 30 кадров в секунду с моим классом FastRobot.

Первый тест:

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

public class TestFBot {
    public static void main(String[] args) {
        try {
            FastRobot fbot = new FastRobot();

            double startTime = System.currentTimeMillis();
            for (int i=0; i < 1000; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

        } catch (AWTException e) {
            e.printStackTrace();
        }
    }
}

Рассмотренные:

Он не делает этого каждый раз, что действительно странно (и расстраивает!). Фактически, он редко делает это вообще с вышеуказанным кодом. Тем не менее, проблема с памятью становится легко воспроизводимой, если у меня есть несколько циклов для возврата назад.

Тест 2

public class TestFBot {
    public static void main(String[] args) {
        try {
            FastRobot fbot = new FastRobot();

            double startTime = System.currentTimeMillis();
            for (int i=0; i < 1000; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

            startTime = System.currentTimeMillis();
            for (int i=0; i < 500; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

            startTime = System.currentTimeMillis();
            for (int i=0; i < 200; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

            startTime = System.currentTimeMillis();
            for (int i=0; i < 1500; i++)
                fbot.createArrayScreenCapture();
            System.out.println("Time taken: " + (System.currentTimeMillis() - startTime)/1000.);

        } catch (AWTException e) {
            e.printStackTrace();
        }
    }
}

Рассмотренные

Неконтролируемая куча теперь воспроизводима, я бы сказал около 80% времени. Я смотрел все, что было в профилировщике, и самое примечательное (я думаю), что сборщик мусора, похоже, останавливается, как только начинается четвертый и заключительный цикл.

Выходной код из приведенного выше кода дал следующие моменты:

Time taken: 24.282    //Loop1
Time taken: 11.294    //Loop2
Time taken: 7.1       //Loop3
Time taken: 70.739    //Loop4

Теперь, если вы суммируете первые три цикла, это добавляет до 42.676, что подозрительно соответствует точному времени, когда сборщик мусора останавливается, и всплеск памяти.

enter image description here

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

Дополнительная информация о профиле

enter image description here

Августо предложил посмотреть на профиль памяти. Есть 1500+ int[], которые перечислены как "недоступные, но еще не собранные". Это, несомненно, массивы int[], создаваемые peer.getRGBPixels(), но по какой-то причине они не уничтожаются. Эта дополнительная информация, к сожалению, только добавляет к моей путанице, поскольку я не уверен, почему GC не будет их собирать.


Профиль с использованием небольшого аргумента кучи -Xmx256m:

В irreputable и горячей лижет предложение я установить максимальный размер кучи к чему-то значительно меньше. Хотя это и мешает ему делать скачок 1 ГБ в использовании памяти, он все равно не объясняет, почему программа вводит в заблуждение свой максимальный размер кучи при входе в 4-ю итерацию.

enter image description here

Как вы можете видеть, точная проблема все еще существует, она просто уменьшилась.;) Проблема с этим решением заключается в том, что программа по какой-то причине все еще питается всей памятью, которую она может - есть также заметное изменение производительности fps с первых итераций, которые потребляют очень мало памяти, и конечная итерация, которая потребляет столько памяти, сколько может.

Остается вопрос, почему он всплывает?


Результаты после нажатия кнопки "Force Garbage Collection":

В предложении jtahlborn я ударил кнопку Force Garbage Collection. Он работал красиво. Он идет от 1 ГБ использования памяти, вплоть до базового уровня 60 мб или около того.

enter image description here

Итак, это, кажется, лекарство. Теперь вопрос в том, как я программно заставляю GC делать это?


Результаты после добавления локальной области Peer в область действия:

В предложении Дэвида Уотерса я изменил функцию createArrayCapture() так, чтобы он содержал локальный объект Peer.

К сожалению, никаких изменений в шаблоне использования памяти.

enter image description here

Все еще становится огромным на 3-й или 4-й итерации.


Анализ пула памяти:

Скриншоты из разных пулов памяти

Все пулы:

All pools

Бассейн Eden:

Eden Pool

Старый генерал:

Old Gen
Почти все использование памяти, похоже, попадает в этот пул.

Примечание. Пространство PS Survivor использовалось (видимо) 0


У меня осталось несколько вопросов:

(a) Граф Профилятора Мусора означает, что я думаю, что это означает? Или я путаю корреляцию с причинностью? Как я уже сказал, я в неизвестной области с этими проблемами.

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

(c) Как это исправить?

Что здесь происходит?

4b9b3361

Ответ 1

Попробуйте вручную указать сборщик мусора.

Concurrent Mark Sweep - это хорошая общая цель, которая обеспечивает хороший баланс между низкой паузой и разумной пропускной способностью.

Если вы используете Java 7 или более позднюю версию Java 6, сборщик G1, вероятно, лучше, так как он также может предотвратить фрагментацию памяти.

Вы можете проверить страницу Java Web Hotspot Virtual Machine Garbage Collection Tuning для получения дополнительной информации и указателей:-D

Ответ 2

Вы сказали, что вы переместили создание объекта из методов в поля класса. Была ли одна из зависимостей, которые вы переместили "равный"? например.

peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);

Вполне возможно, что сверстник держится за все скриншоты, сделанные для жизни объекта, это очистится, когда peer выйдет из сферы действия, конец метода в Robot, жизнь класса для FastRobot.

Попробуйте переместить создание и область действия peer обратно в ваш метод и посмотреть, какая разница.

public int[] createArrayScreenCapture() throws HeadlessException, AWTException {
        RobotPeer localPeer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
        return localPeer.getRGBPixels(screenRect);
}

Попытка 2

Итак, это, кажется, лекарство. Вопрос в том, как я могу грамматически заставить GC сделать это?

Вы можете вызвать System.gc(), чтобы запросить сбор мусора. Обратите внимание, что это запрос, а не требование. JVM будет запускать сборку мусора, если она думает, что она стоит.

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

Остается вопрос, почему это всплывает вообще?

JVM будет пытаться и только запускать Major Garbage Collection, когда это абсолютно необходимо (большая часть используемой кучи). (Прочитайте в "Молодое поколение" против "Старое поколение" и "Молодое поколение", "Пространство Иден" и пространство для оставшихся в живых). Поэтому ожидайте, что долговечные или голодные Java-приложения будут сидеть рядом с максимальным размером кучи. Стоит отметить, что для того, чтобы память переместилась в Старое Поколение, он должен был выжить 3 небольших сеанса GC (Eden = > Survivor 1 = > Survivor 2 = > Old Generation [В зависимости от того, какой JVM вы используете и какую схему GC вы выделили по аргументам командной строки.])

Что касается изменения этого поведения, это может быть любое количество вещей. Этот последний цикл является самым длинным, делает ли System.getCurrentTimeMillis() блок достаточно долго, чтобы GC мог перейти в другой поток? Значит, проблема проявляется только в более длинных циклах? Процесс снятия снимка экрана звучит довольно низко для меня, я предполагаю, что реализовано с вызовом ядра операционной системы, блокирует ли это процесс в пространстве ядер, предотвращая запуск других потоков? (что остановит gc, работающий в фоновом потоке).

Посмотрите http://www.javacodegeeks.com/2012/01/practical-garbage-collection-part-1.html для ознакомления с миром сбора мусора. Или Java Memory объяснил (SUN JVM) для кучи ссылок.

Надеюсь, что это помогло.