Здесь проблема:
Как вы можете видеть, воздушные шары использования памяти вышли из-под контроля! Я должен был добавить аргументы в 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, что подозрительно соответствует точному времени, когда сборщик мусора останавливается, и всплеск памяти.
Теперь это мой первый родео с профилированием, не говоря уже о том, как я впервые задумывался о сборке мусора - это всегда было что-то вроде волшебного в фоновом режиме - так что я не знаю, что, если что-нибудь, я узнал.
Дополнительная информация о профиле
Августо предложил посмотреть на профиль памяти. Есть 1500+ int[]
, которые перечислены как "недоступные, но еще не собранные". Это, несомненно, массивы int[]
, создаваемые peer.getRGBPixels()
, но по какой-то причине они не уничтожаются. Эта дополнительная информация, к сожалению, только добавляет к моей путанице, поскольку я не уверен, почему GC не будет их собирать.
Профиль с использованием небольшого аргумента кучи -Xmx256m:
В irreputable и горячей лижет предложение я установить максимальный размер кучи к чему-то значительно меньше. Хотя это и мешает ему делать скачок 1 ГБ в использовании памяти, он все равно не объясняет, почему программа вводит в заблуждение свой максимальный размер кучи при входе в 4-ю итерацию.
Как вы можете видеть, точная проблема все еще существует, она просто уменьшилась.;) Проблема с этим решением заключается в том, что программа по какой-то причине все еще питается всей памятью, которую она может - есть также заметное изменение производительности fps с первых итераций, которые потребляют очень мало памяти, и конечная итерация, которая потребляет столько памяти, сколько может.
Остается вопрос, почему он всплывает?
Результаты после нажатия кнопки "Force Garbage Collection":
В предложении jtahlborn я ударил кнопку Force Garbage Collection. Он работал красиво. Он идет от 1 ГБ использования памяти, вплоть до базового уровня 60 мб или около того.
Итак, это, кажется, лекарство. Теперь вопрос в том, как я программно заставляю GC делать это?
Результаты после добавления локальной области Peer в область действия:
В предложении Дэвида Уотерса я изменил функцию createArrayCapture()
так, чтобы он содержал локальный объект Peer
.
К сожалению, никаких изменений в шаблоне использования памяти.
Все еще становится огромным на 3-й или 4-й итерации.
Анализ пула памяти:
Скриншоты из разных пулов памяти
Все пулы:
Бассейн Eden:
Старый генерал:
Почти все использование памяти, похоже, попадает в этот пул.
Примечание. Пространство PS Survivor использовалось (видимо) 0
У меня осталось несколько вопросов:
(a) Граф Профилятора Мусора означает, что я думаю, что это означает? Или я путаю корреляцию с причинностью? Как я уже сказал, я в неизвестной области с этими проблемами.
(b) Если это сборщик мусора... что мне с этим делать? Почему он вообще останавливается, а затем работает со сниженной скоростью для оставшейся части программы?
(c) Как это исправить?
Что здесь происходит?