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

时间:2013-02-28 16:46:15

标签: java memory-leaks garbage-collection jvm

问题在于:

the memory usage balloons out of control

如您所见,内存使用情况失控!我必须向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,这可疑地对应于垃圾收集器停止的确切时间和内存峰值。

enter image description here

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

其他个人资料信息

enter image description here

Augusto建议查看内存配置文件。有1500多个int[]被列为&#34;无法访问,但尚未收集。&#34;这些肯定是int[]创建的peer.getRGBPixels()数组,但出于某种原因,它们并未被销毁。不幸的是,这些额外的信息只会增加我的困惑,因为我不确定为什么 GC无法收集它们


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

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

enter image description here

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

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


击中&#34;强制垃圾收集后的结果&#34;按钮:

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

enter image description here

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


将本地Peer添加到功能范围后的结果:

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

不幸的是内存使用模式没有变化。

enter image description here

在第3次或第4次迭代中仍然很大。


内存池分析:

来自不同内存池的ScreenShots

所有泳池:

All pools

伊甸园游泳池:

Eden Pool

Old Gen:

Old Gen
几乎所有的内存使用量似乎都落在这个池中。

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


我还有几个问题:

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

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

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

这里发生了什么?

2 个答案:

答案 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)以获得更多链接。

希望这有所帮助。