大量内存出血,导致堆大小在大约8秒内从约64mb变为1.5gb。垃圾回收器有问题?测试 2内存池分析:
问题出在:
如您所见,内存使用量激增失控!我不得不向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 循环背靠背,内存问题变得很容易重现。
测试 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多个被列为“无法访问,但尚未收集”。这些肯定是他们创建的数组,但由于某种原因,它们没有被破坏。不幸的是,这些额外的信息只会增加我的困惑,因为我不确定为什么GC不会收集它们int[]
int[]
peer.getRGBPixels()
使用小堆参数 -Xmx256m 的配置文件:
在无可辩驳和 Hot Licks 的建议下,我将最大堆大小设置为明显较小的值。虽然这确实可以防止它在内存使用量上跳跃1gb,但它仍然没有解释为什么程序在进入第4次迭代时膨胀到其最大堆大小。
如您所见,确切的问题仍然存在,它只是变得更小了。;)这个解决方案的问题在于,由于某种原因,程序仍然消耗了它所能消耗的所有内存 - 与第一次迭代相比,fps性能也发生了显着变化,前者消耗很少的内存,而最终的迭代则消耗尽可能多的内存。
问题仍然是为什么它会膨胀?
点击“强制垃圾回收”按钮后的结果:
在jtahlborn的建议下,我按下了“强制垃圾收集”按钮。它工作得很好。它从1gb的内存使用量下降到60mb左右的基线。
所以,这似乎是治愈的方法。现在的问题是,如何以编程方式强制GC执行此操作?
将本地 Peer 添加到函数的作用域后的结果:
在David Waters的建议下,我修改了函数,使其包含本地对象。createArrayCapture()
Peer
遗憾的是,内存使用模式没有变化。
在第3次或第4次迭代中仍然变得巨大。
内存池分析:
来自不同内存池的屏幕截图
所有池:
伊甸园池:
老将军:
几乎所有的内存使用量似乎都属于这个池。
注意:PS Survivor Space(显然)有0次使用
我剩下几个问题:
(a) 垃圾探查器图是否意味着我认为它意味着什么?还是我把相关性与因果关系混为一谈?正如我所说,我有这些问题处于一个未知的领域。
(b) 如果是垃圾回收器...我该怎么办..?为什么它完全停止,然后在程序的其余部分以较低的速率运行?
(c) 如何解决此问题?
这是怎么回事?