如您所见,内存使用情况失控!我必须向JVM添加参数以增加堆大小以避免内存错误,同时我弄清楚发生了什么。不好!
此应用程序(最终)将用于基本的屏幕CV和模板匹配类型的东西以用于自动化目的。我想在观看屏幕时实现尽可能高的帧速率,并通过一系列独立的消费者线程处理所有处理。
我很快发现股票机器人类的速度非常可怕,所以我打开了源代码,取出了所有重复的工作并浪费了开销,并将其重建为我自己的名为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类的~30fps。
当我在主程序中开始出现内存错误时,我设置了这个非常简单的测试来关注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循环背靠背,则内存问题变得很容易重现。
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,这可疑地对应于垃圾收集器停止的确切时间和内存峰值。
现在,这是我第一次进行剖析的牛仔竞技表演,更不用说我第一次想到垃圾收集了 - 它总是在背景中神奇地起作用 - 所以,我和#39;我不确定我发现了什么,如果有的话。
Augusto建议查看内存配置文件。有1500多个int[]
被列为&#34;无法访问,但尚未收集。&#34;这些肯定是int[]
创建的peer.getRGBPixels()
数组,但出于某种原因,它们并未被销毁。不幸的是,这些额外的信息只会增加我的困惑,因为我不确定为什么 GC无法收集它们
在无可争议的Hot Licks建议中,我将最大堆大小设置为显着更小的值。虽然这个 阻止它使内存使用量增加1gb,但它仍然无法解释为什么程序在进入第4个时膨胀到其最大堆大小迭代。
正如您所看到的,确切的问题仍然存在,它只是变小了。 ;)这个解决方案的问题在于,由于某种原因,程序仍然在吞噬所有内存 - 从第一次迭代开始,fps性能也发生了显着变化,这会消耗很少的内存,并且最后的迭代,它消耗尽可能多的内存。
问题仍然是为什么它会膨胀?
根据jtahlborn的建议,我点击了 Force Garbage Collection 按钮。它工作得很漂亮。它从1GB的内存使用量下降到60mb左右的基线。
所以,这似乎是治愈方法。现在的问题是,我如何以编程方式强制GC执行此操作?
在David Waters建议中,我修改了createArrayCapture()
函数,使其保存了一个本地Peer
对象。
不幸的是内存使用模式没有变化。
在第3次或第4次迭代中仍然很大。
几乎所有的内存使用量似乎都落在这个池中。
注意:PS Survivor Space(显然)有0次使用
(a)Garbage Profiler图表是否意味着我的意思?还是我混淆与因果关系的相关性?正如我所说,我在这个问题的未知领域。
(b)如果它 垃圾收集器......我该怎么办呢?为什么它会完全停止,然后以较低的速率运行程序的其余部分?
(c)我该如何解决这个问题?
这里发生了什么?
答案 0 :(得分:4)
尝试手动指定垃圾收集器。
Concurrent Mark Sweep是一个很好的通用目标,可以在低暂停和合理的吞吐量之间提供良好的平衡。
如果您使用的是Java 7或更高版本的Java 6,则G1收集器可能更好,因为它还可以防止内存碎片。
您可以查看Java SE Hotspot Virtual Machine Garbage Collection Tuning页面以获取更多信息和指示:-D
答案 1 :(得分:3)
您说过将对象创建从方法移动到类的字段。是你移动的“同伴”之一吗? e.g。
peer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
很可能是对手持有对象生命周期所拍摄的所有截图,当对等体移出范围时,这将被清除,机器人中方法的结束,FastRobot类的生命。 / p>
尝试将对等的创建和范围移回到您的方法,看看有什么区别。
public int[] createArrayScreenCapture() throws HeadlessException, AWTException {
RobotPeer localPeer = ((ComponentFactory)toolkit).createRobot(elRoboto, screen);
return localPeer.getRGBPixels(screenRect);
}
尝试2
所以,这似乎是治愈方法。现在的问题是,我如何亲 语法强制GC执行此操作?
您可以调用System.gc()来请求垃圾回收。请注意,这是请求,而不是需求。如果JVM认为它的价值,它将只运行垃圾收集。
正如您所看到的,确切的问题仍然存在,它刚刚完成 小。 ;)这个解决方案的问题是该程序,对于一些人 理由是,它仍然可以通过所有的记忆来吃 - 有 从第一次迭代开始,fps性能也发生了显着变化, 消耗很少的内存,最后的迭代,这 消耗尽可能多的内存。
问题仍然是为什么它会膨胀?
JVM将尝试在绝对必要时(大多数使用堆)运行主要垃圾收集。 (阅读年轻一代与老一代,年轻一代,伊甸园空间和幸存者空间)。所以期望长寿或内存饥饿的java应用程序坐在最大堆大小附近。值得注意的是,对于要进入老一代的内存,它必须能够幸存3次小型GC运行(Eden =&gt; Survivor 1 =&gt; Survivor 2 =&gt; Old Generation [取决于您运行的JVM和GC方案)你选择了命令行参数。])
至于为什么这种行为发生了变化,它可能是任何数量的东西。最后一个循环是最长的,System.getCurrentTimeMillis()块是否足够长,以便GC能够使用不同的线程?所以问题只出现在较长的循环中?对我来说拍摄屏幕拍摄声音的过程相当低,我假设通过调用操作系统kernal来实现,这是否会阻止内核空间中的进程阻止其他线程运行? (这将阻止gc在后台线程中运行。)
请查看http://www.javacodegeeks.com/2012/01/practical-garbage-collection-part-1.html,了解垃圾收集世界。或Java Memory explained (SUN JVM)以获得更多链接。
希望这有所帮助。