大量记忆出血导致堆大小从大约64mb变为1.5gb,大约8秒.垃圾收集器的问题?

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循环背靠背,则内存问题变得很容易重现.

测试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();
        }
    }
}
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不会收集它们


使用小堆参数-Xmx256m的配置文件:

在无可争议的Hot Licks建议中,我将最大堆大小设置为显着更小的值.虽然这确实阻止了内存使用量的1gb跳跃,但它仍然无法解释为什么程序在进入第4次迭代时膨胀到其最大堆大小.

在此输入图像描述

正如您所看到的,确切的问题仍然存在,它只是变小了.;)这个解决方案的问题在于,由于某种原因,程序仍然在吞噬所有内存 - 从第一次迭代开始,fps性能也发生了显着变化,这会消耗很少的内存,并且最后的迭代,它消耗尽可能多的内存.

问题仍然是为什么它会膨胀?


点击"Force Garbage Collection"按钮后的结果:

在jtahlborn的建议下,我点击了Force Garbage Collection按钮.它工作得很漂亮.它从1GB的内存使用量下降到60mb左右的基线.

在此输入图像描述

所以,这似乎是治愈方法.现在的问题是,我如何以编程方式强制GC执行此操作?


将本地Peer添加到函数范围后的结果:

在David Waters的建议中,我修改了createArrayCapture()函数,使其保存了一个本地Peer对象.

遗憾的是内存使用模式没有变化.

在此输入图像描述

在第3次或第4次迭代中仍然变得很大.


内存池分析:

ScreenShots来自不同的内存池

所有泳池:

所有泳池

伊甸园游泳池:

伊甸园游泳池

老根:

老根
几乎所有的内存使用量似乎都落在这个池中.

注意:PS Survivor Space(显然)有0次使用


我有几个问题:

(a)Garbage Profiler图表是否意味着我的意思?还是我混淆与因果关系的相关性?正如我所说,我处于这个问题的未知领域.

(b)如果垃圾收集器......我该怎么办呢?为什么它完全停止,然后以减少的速率运行程序的其余部分?

(c)我该如何解决这个问题?

这里发生了什么?

dro*_*.ah 4

尝试手动指定垃圾收集器。

并发标记扫描是一种很好的通用目的,它在低暂停和合理吞吐量之间提供了良好的平衡。

如果您使用 Java 7 或更高版本的 Java 6,G1 收集器可能更好,因为它还能够防止内存碎片。

您可以查看Java SE Hotspot Virtual Machine Garbage Collection Tuning页面以获取更多信息和指导 :-D