这是问题所在:
如您所见,内存使用膨胀失控!我不得不向 JVM 添加参数以增加堆大小,以避免在我弄清楚发生了什么时出现内存不足错误。不好!
基本应用总结(上下文)
这个应用程序(最终)将用于基本的屏幕简历和模板匹配类型的东西,以实现自动化目的。我想实现尽可能高的帧速率来观看屏幕,并通过一系列单独的消费者线程处理所有处理。
我很快发现库存的 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 fps 到我的 FastRobot 类的约 30 fps。
第一次测试:
当我在主程序中开始出现内存不足错误时,我设置了这个非常简单的测试来监视 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();
}
}
}
检查:
它不会每次都这样做,这真的很奇怪(而且令人沮丧!)。事实上,它很少用上面的代码做到这一点。但是,如果我有多个 for 循环背靠背 ,则内存问题很容易重现。
测试 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,这可疑地对应于垃圾收集器停止的确切时间,以及内存峰值。
现在,这是我第一次进行性能分析,更不用说我第一次想到垃圾收集——它总是在后台神奇地工作——所以,我不确定是什么,如果有的话,我已经发现了。
其他个人资料信息
Augusto 建议查看内存配置文件。有 1500 多个int[]
被列为“无法访问,但尚未收集”。这些肯定是创建的int[]
数组peer.getRGBPixels()
,但由于某种原因,它们没有被破坏。不幸的是,这些附加信息只会增加我的困惑,因为我不确定为什么GC 不会收集它们
使用小堆参数 -Xmx256m 的配置文件:
在声名狼藉和 Hot Licks 的建议下,我将最大堆大小设置为明显更小。虽然这确实阻止了它在内存使用量上的 1gb 跳跃,但它仍然没有解释为什么程序在进入第 4 次迭代时会膨胀到其最大堆大小。
如您所见,确切的问题仍然存在,只是变小了。;) 这个解决方案的问题是,由于某种原因,程序仍在消耗它所能消耗的所有内存——从第一次迭代开始,fps 性能也有显着变化,消耗的内存非常少,并且最后一次迭代,它会消耗尽可能多的内存。
问题仍然是为什么它会膨胀?
点击“强制垃圾收集”按钮后的结果:
在 jtahlborn 的建议下,我点击了强制垃圾收集按钮。它工作得很好。它从 1gb 的内存使用量下降到 60mb 左右的基线。
所以,这似乎是治疗方法。现在的问题是,我如何以编程方式强制 GC 执行此操作?
将本地 Peer 添加到函数范围后的结果:
在 David Waters 的建议下,我修改了该createArrayCapture()
函数,使其拥有一个本地Peer
对象。
不幸的是,内存使用模式没有变化。
在第 3 次或第 4 次迭代中仍然会变得很大。
内存池分析:
来自不同内存池的屏幕截图
所有游泳池:
伊甸池:
老一代:
几乎所有的内存使用似乎都在这个池中。
注意:PS 幸存者空间(显然)使用率为 0
我有几个问题:
(a) Garbage Profiler 图的意思是我认为的意思吗?还是我混淆了因果关系的相关性?正如我所说,我在这些问题的未知领域。
(b) 如果是垃圾收集器……我该怎么办……?为什么它完全停止,然后在程序的其余部分以降低的速度运行?
(c) 我该如何解决这个问题?
这里发生了什么?