Java与LibGDX字体的内存泄漏问题

4

我认为LibGDX中释放BitmapFonts存在问题。

我创建了以下测试:

printAnalytics();

ArrayList<BitmapFont> fonts = new ArrayList<BitmapFont>();
for (int i = 0; i < 2000; i++) {
  // create a sample parameters
  FreeTypeFontParameter params = new FreeTypeFontParameter();
  params.size = 12;
  params.minFilter = TextureFilter.Nearest;
  params.magFilter = TextureFilter.Nearest;

  // load the font
  FileHandle fontFile = Gdx.files.internal("arial.ttf");

  // generate it
  FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);
  BitmapFont bitmapFont = generator.generateFont(params);
  bitmapFont.setUseIntegerPositions(false);
  generator.dispose();

  // add to array
  fonts.add(bitmapFont);
}

printAnalytics();
// dispose all fonts
for (BitmapFont font : fonts) {
  font.dispose();
}
// clear array
fonts.clear();
fonts = null;

printAnalytics();

如您所见,我在加载字体之前输出了一些内存信息。然后我创建了2,000个BitmapFonts并再次输出了信息。销毁字体并删除数组(以丢失所有实例),然后再次输出内存信息。我正在使用默认的Arial字体进行测试。

这是printAnalytics方法的代码:

public void printAnalytics() {
  int mb = 1024 * 1024;

  // get Runtime instance
  Runtime instance = Runtime.getRuntime();

  System.out.println("\n***** Heap utilization statistics [MB] *****\n");

  // available memory
  System.out.println("Total Memory: " + (instance.totalMemory() / mb));

  // free memory
  System.out.println("Free Memory: " + (instance.freeMemory() / mb));

  // used memory
  System.out.println("Used Memory: " + ((instance.totalMemory() - instance.freeMemory()) / mb));

  // Maximum available memory
  System.out.println("Max Memory: " + (instance.maxMemory() / mb));

  // Gdx heap
  System.out.println("Gdx Java Heap: " + (Gdx.app.getJavaHeap() / mb));

  // Gdx heap
  System.out.println("Gdx Native Heap: " + (Gdx.app.getNativeHeap() / mb));
}

这是我的输出结果:

***** Heap utilization statistics [MB] *****

Total Memory: 120
Free Memory: 91
Used Memory: 28
Max Memory: 1790
Gdx Java Heap: 28
Gdx Native Heap: 28

***** Heap utilization statistics [MB] *****

Total Memory: 232
Free Memory: 109
Used Memory: 122
Max Memory: 1790
Gdx Java Heap: 122
Gdx Native Heap: 122

***** Heap utilization statistics [MB] *****

Total Memory: 232
Free Memory: 109
Used Memory: 122
Max Memory: 1790
Gdx Java Heap: 122
Gdx Native Heap: 122

以下是任务管理器的数字:

  • 测试前: 165 MB (我正在测试我的一个应用程序)
  • 加载字体后: 800 MB
  • 释放后: 340 MB

你可以看到问题所在: 这个测试前后使用的内存应该是相同的。但事实并非如此。它增加了150/200 MB!

我检查了BitmapFont是否拥有纹理(font.ownsTexture()),结果为true,因此dispose方法也应该处理创建的字体纹理。

我知道JVM使用一些RAM,这就是为什么任务管理器显示比代码中大的数字。但为什么最终使用的Java堆内存增加了94MB (122-28),进程内存增加了175MB (340-165)呢?

我是在错误地加载或释放东西吗?是Java的问题还是LibGDX的问题?这是一个严重的问题,因为在我的程序的某个部分,我需要加载和卸载很多字体...

更新:

我跳过了数组部分: 我创建了字体bitmapFont,然后立即调用了bitmapFont.dispose()bitmapFont=null。进程内存没有改变,使用的堆空间也保持不变。为什么?!

我正在运行Windows 7,Java 7。

1个回答

3

有趣的问题,我稍微改了一下你的测试,基本上将所有不属于测试本身的内容(如字体加载、参数等)移动到循环外。我还在循环前后添加了System.gc()调用,这样我们就可以看到实际使用的内存。

            printAnalytics("before test");

            Array<BitmapFont> fonts = new Array<BitmapFont>();
            // create a sample parameters
            FreeTypeFontGenerator.FreeTypeFontParameter params = new FreeTypeFontGenerator.FreeTypeFontParameter();
            params.size = 12;
            params.minFilter = Texture.TextureFilter.Nearest;
            params.magFilter = Texture.TextureFilter.Nearest;

            // load the font
            FileHandle fontFile = Gdx.files.internal("arial.ttf");
            for (int i = 0; i < 2000; i++) {
                // generate it
                FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);
                BitmapFont bitmapFont = generator.generateFont(params);
                bitmapFont.setUseIntegerPositions(false);
                generator.dispose();

                // add to array
                fonts.add(bitmapFont);
            }

            printAnalytics("before disposing, before first gc");
            System.gc();
            printAnalytics("before disposing, after first gc");
            // dispose all fonts
            for (BitmapFont font : fonts) {
                font.dispose();
            }
            // clear array
            fonts.clear();
            printAnalytics("after disposing, before second gc");
            System.gc();
            printAnalytics("after disposing, after second gc");

我稍微修改了你的printAnalytics函数,使它还能报告正在测量的当前事件。
    public static void printAnalytics(String event) {
        ...
        System.out.println("\n***** Heap utilization (" + event + ") statistics [MB] *****\n");

以下是我的结果:

***** Heap utilization (before test) statistics [MB] *****

Total Memory: 180
Free Memory: 165
Used Memory: 15
Max Memory: 2647
Gdx Java Heap: 15
Gdx Native Heap: 15

***** Heap utilization (before disposing, before first gc) statistics [MB] *****

Total Memory: 180
Free Memory: 101
Used Memory: 78
Max Memory: 2647
Gdx Java Heap: 78
Gdx Native Heap: 78

***** Heap utilization (before disposing, after first gc) statistics [MB] *****

Total Memory: 180
Free Memory: 132
Used Memory: 48
Max Memory: 2647
Gdx Java Heap: 48
Gdx Native Heap: 48

***** Heap utilization (after disposing, before second gc) statistics [MB] *****

Total Memory: 180
Free Memory: 131
Used Memory: 48
Max Memory: 2647
Gdx Java Heap: 48
Gdx Native Heap: 48

***** Heap utilization (after disposing, after second gc) statistics [MB] *****

Total Memory: 180
Free Memory: 178
Used Memory: 2
Max Memory: 2647
Gdx Java Heap: 2
Gdx Native Heap: 2

似乎使用dispose方法确实有所帮助,但是除非调用System.gc()方法,否则Java会自行决定何时释放内存。

1
很酷。我测试了一下 - 你是对的,gc起到了作用 :) 奇怪的是任务管理器中的内存仍然保持不变 - 300MB。 - Yasen Bagalev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接