Zac*_*aro 32 java garbage-collection jvm memory-leaks

如您所见,内存使用情况失控!我必须向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;
}
}
Run Code Online (Sandbox Code Playgroud)
本质上,我从原来的所有变化都是从函数体中移动许多分配,并将它们设置为类的属性,因此它们不会每次都被调用.这样做实际上对帧速率有显着影响.即使在我的笔记本电脑也非常强劲的情况下,它的机器人类库存从~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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
它不是每次都这样做,这真的很奇怪(令人沮丧!).事实上,它很少使用上面的代码.但是,如果我有多个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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
失控堆现在是可重现的,我会说大约80%的时间.我看了所有虽然探查,最说明(我认为)的事情是,垃圾收集站貌似正确的第四个也是最后循环开始.
上面代码的输出给出了以下时间:
Time taken: 24.282 //Loop1
Time taken: 11.294 //Loop2
Time taken: 7.1 //Loop3
Time taken: 70.739 //Loop4
Run Code Online (Sandbox Code Playgroud)
现在,如果你总结前三个循环,它总计最多42.676,这可疑地对应于垃圾收集器停止的确切时间和内存峰值.

现在,这是我第一次进行剖析的牛仔竞技表演,更不用说我第一次考虑垃圾收集了 - 它总是在背景中神奇地起作用 - 所以,我不确定是什么,如果有的话,我发现了.

奥古斯托建议查看内存配置文件.有1500多个int[]被列为"无法访问,但尚未收集".这些肯定是创建的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)我该如何解决这个问题?
这里发生了什么?
尝试手动指定垃圾收集器。
并发标记扫描是一种很好的通用目的,它在低暂停和合理吞吐量之间提供了良好的平衡。
如果您使用 Java 7 或更高版本的 Java 6,G1 收集器可能更好,因为它还能够防止内存碎片。
您可以查看Java SE Hotspot Virtual Machine Garbage Collection Tuning页面以获取更多信息和指导 :-D