JVM:非堆内存导致OutOfMemoryError,因为垃圾收集器不是运行。我究竟做错了什么?

JVM: Non-heap memory causes OutOfMemoryError because the Garbage Collector is not run. What am I doing wrong?

我遇到了一个奇怪的问题,我正在处理的 Groovy 应用程序增长到消耗更多内存(远远超出 xmx 参数的限制,所以它不能成为堆)直到计算机 运行 内存不足,此时 JVM 会以两种不同的方式之一运行——它要么突然释放(几乎)所有它占用的内存,要么因 OutOfMemoryError 而崩溃。这个问题可以通过定期调用 System.gc().

来避免

发生在我​​身上的事情似乎是,尽管分配了越来越多的内存,但 JVM 并没有调用垃圾收集器。目前尚不清楚它是否总是(尝试)在计算机 运行 内存不足时调用它并且只是有时会成功,或者它是否有时会在不调用 GC 的情况下抛出 OOME(即使这会违反规范) .有趣的是,ResourceMonitor not 报告 java.exe 实例提交了更多内存(它保持在 ~500MB),但提交费用仍然上升。

在此过程中,我唯一要做的就是让计时器每 33 毫秒启动一个新线程,以调用 JComponent 的 repaint()。我听说每个新线程都在堆外分配了一些内存,所以我怀疑问题可能是那些内存从未被收集过,但我可能是错的(我真的觉得我的深度在这里,TBH)。

我显然可以通过让计时器定期调用 System.gc() 来解决问题(尽管不少于每隔几秒一次),但这对我来说似乎是非常糟糕的做法,老实说希望我做错了什么,而不是 JVM 出现了一些奇怪的问题。

我当时 运行 唯一的代码是下面的代码(我删除了一些注释和一些登录到控制台的代码)。当然,还有一大堆更多的代码,但正如所提到的,唯一活跃的是调用 repaint() 的定时器。

//snip: package and imports
    
@groovy.transform.CompileStatic
class MapWindow extends BasicWindow {
//BasicWindow provides a constructor that stores its two arguments as windowX and windowY (and set dimensions accordingly) and creates and stores a basic frame
//It also overrides setVisible() to call frame.setVisibile() as well. It does nothing else.

    int xPos
    int yPos

    MapWindow(int x, int y) {
        super(x, y)
        frame.setTitle("EFG")
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
        frame.pack()
    }

    @Override
    public void paint(Graphics gA) {
        VolatileImage img = createVolatileImage(windowX, windowY)
        Graphics2D g = (Graphics2D) img.getGraphics()

        VolatileImage tmp = getTestImage()

        g.drawImage(tmp, 0, 0, null)
        g.dispose()
        gA.drawImage(img, 0, 0, windowX, windowY, null)

        if (Game.game.rnd.nextInt(100) == 0) {
            //System.gc()  <--If I uncomment this, things work
        }
    }

    VolatileImage getTestImage() {
        VolatileImage img = createVolatileImage(windowX, windowY)
        Graphics2D g = img.createGraphics()

        for (int x = 0; x < tileSet.x; x++) {
            for (int y = 0; y < tileSet.y; y++) {
                g.drawImage(tileSet.images[x][y], x * tileSet.resolution, y * tileSet.resolution, null)
            }
        }

        char[] msg = "test complete".toCharArray()
        for (int x = 0; x < msg.length; x++) {
            char c = msg[x]
            if (!c.isWhitespace()) {
                g.drawImage(tileSet.getImage("symbol.$c"), x * tileSet.resolution, tileSet.resolution * tileSet.y, null)
            }
        }

        g.dispose()

        return img
    }
}

    //Located in a different class, called once during startup. It also subject to a @CompileStatic
    void startTimer() {
        timer = new java.util.Timer()
        int period = config.getInt("framePeriod")
        boolean fixed = config.getBoolean("frameFixedRate")
        if (fixed) {
            timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    activeWindow?.frame?.repaint()
                }
            }, period, period)
        } else {
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    activeWindow?.frame?.repaint()
                }
            }, period, period)
        }
    }

如果需要,我可以提供更多 code/info,但我不想通过发布整个程序来阻塞它。问题似乎极有可能出在这里,可能在 paint() 或 getTestImage() (或 JVM)中。

Windows 7 64 位
16GB 内存,无页面文件
SDK 1.8.0_25(问题也已通过 13.0.1 确认)
Groovy2.5.8
我使用 IntelliJ IDEA,但如果我构建一个 .jar 并独立 运行 它也会出现此问题。

编辑: ewramner 指出我应该在(或重用)VolatileImages 上调用 flush(),因此我接受了它作为解决方案。如果有人能解释 为什么 GC 不更早采取行动,我仍然很感兴趣,特别是如果它导致 JVM 因 OOME 而崩溃。

如果您阅读 VolatileImage 的文档,它说:

When a VolatileImage object is created, limited system resources such as video memory (VRAM) may be allocated in order to support the image. When a VolatileImage object is no longer used, it may be garbage-collected and those system resources will be returned, but this process does not happen at guaranteed times. Applications that create many VolatileImage objects (for example, a resizing window may force recreation of its back buffer as the size changes) may run out of optimal system resources for new VolatileImage objects simply because the old objects have not yet been removed from the system.

解决方案是调用 flush(您调用 System.gc 的地方)或者可能在每次绘制操作中重复使用图像而不是 re-creating。