groovy JVM:非堆内存导致OutOfMemoryError,因为垃圾收集器未运行,我做错了什么?

6qqygrtg  于 2022-11-01  发布在  其他
关注(0)|答案(1)|浏览(209)

我遇到了一个奇怪的问题,我正在使用的Groovy应用程序增长时占用了更多的内存(远远超出了xmx参数的限制,因此它不能是堆),直到计算机用完RAM,此时JVM以两种不同方式之一运行--它要么突然释放(几乎)所有的内存,或者它崩溃并出现OutOfMemoryError。这个问题可以通过定期调用System.gc()来避免。
在我看来,尽管分配了越来越多的内存,JVM并没有调用垃圾收集器。一旦计算机的RAM用完就(试图)调用它,或者它是否有时在不调用GC的情况下引发OOME(即使这会违反规范)。值得注意的是,ResourceMonitor * 不 * 报告java.exe示例提交了更多的内存(它保持在~ 500 MB),但提交费用还是会上升。
在此过程中,我所做的唯一事情是让Timer每隔33 ms启动一个新线程来调用JComponent的repaint()。我听说每个新线程都被分配了堆外的一些内存,所以我怀疑问题可能是内存从未被收集,但我可能是错的(我真的觉得我在这里没有深度,TBH)。
很明显,我可以通过让一个Timer定期调用System.gc()来解决问题(尽管频率不低于每隔几秒一次),但对我来说,这似乎是非常糟糕的做法,我真诚地希望我做错了什么,而不是JVM出现了什么奇怪的问题。
我当时运行的唯一代码是下面的代码(我已经删除了一些注解和一些记录到控制台的代码)。当然,还有一大堆代码,但唯一活动的是,正如前面提到的,调用repaint()的Timer。

//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)
        }
    }

如果需要的话,我可以提供更多的代码/信息,但是我不想通过发布整个程序来阻塞这个过程。看起来问题很可能在这里的某个地方,可能在paint()或getTestImage()(或JVM)中。
Windows 7 64位操作系统
16 GB RAM,无页面文件
SDK 1.8.0_25(问题也在13.0.1中得到确认)
非常好2.5.8
我使用IntelliJ IDEA,但是如果我构建一个.jar并独立运行它,也会出现这个问题。
编辑:ewramner已经指出我应该在(或者重用)VolatileImages上调用flush(),所以我已经接受了这个解决方案。如果有人能解释 * 为什么 * GC没有更早地行动,我仍然很感兴趣,特别是如果它导致JVM因OOME而崩溃的话。

m0rkklqb

m0rkklqb1#

如果您阅读了VolatileImage的文档,它会说:
当创建VolatileImage对象时,有限的系统资源(如视频内存(VRAM)。当不再使用VolatileImage对象时,它可能会被垃圾回收,并返回那些系统资源,但此过程不会在保证的时间发生。创建许多VolatileImage对象的应用程序(例如,当大小改变时,调整大小的窗口可能强制重新创建其后台缓冲区)可能会用完新的VolatileImage对象的最佳系统资源,这仅仅是因为旧对象尚未从系统中删除。
解决方案是调用flush(在这里调用System.gc),或者重用图像,而不是为每个绘制操作重新创建图像。

相关问题