Java无法垃圾回收内存。

15 浏览
0 Comments

Java无法垃圾回收内存。

我正在读取一个非常大的文件,并从每行中提取一些小段文本。但是在操作结束时,我留下的内存很少,无法使用。似乎垃圾收集器在读取文件后无法释放内存。

我的问题是:有没有办法释放这个内存?或者这是JVM的bug吗?

我创建了一份SSCCE来演示这个问题。它读取一个1 MB(Java中2 MB由于16位编码)的文件,并从每行中提取一个字符(约4000行,应该约为8 KB)。在测试结束时,仍然使用了完整的2 MB内存!

初始内存使用:

Allocated: 93847.55 kb
Free: 93357.23 kb

在读取文件之后(在任何手动垃圾收集之前):

Allocated: 93847.55 kb
Free: 77613.45 kb (~16mb used)

这是可以预料的,因为程序使用了大量资源来读取文件。

然后我进行垃圾收集,但并非所有内存都被释放:

Allocated: 93847.55 kb
Free: 91214.78 kb (~2 mb used! That's the entire file!)

我知道手动调用垃圾收集器不能保证任何结果(在某些情况下,它是慵懒的)。但是在我的大型应用程序中发生这种情况,文件占用了几乎所有可用内存,并导致程序的其余部分由于需要而耗尽内存。这个例子证实了我的怀疑,即从文件中读取的多余数据没有被释放。

下面是生成测试的SSCCE:

import java.io.*;
import java.util.*;
public class Test {
    public static void main(String[] args) throws Throwable {
        Runtime rt = Runtime.getRuntime();
        double alloc = rt.totalMemory()/1000.0;
        double free = rt.freeMemory()/1000.0;
        System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);
        Scanner in = new Scanner(new File("my_file.txt"));
        ArrayList al = new ArrayList();
        while(in.hasNextLine()) {
            String s = in.nextLine();
            al.add(s.substring(0,1)); // extracts first 1 character
        }
        alloc = rt.totalMemory()/1000.0;
        free = rt.freeMemory()/1000.0;
        System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);
        in.close();
        System.gc();
        alloc = rt.totalMemory()/1000.0;
        free = rt.freeMemory()/1000.0;
        System.out.printf("Allocated: %.2f kb\nFree: %.2f kb\n\n",alloc,free);
    }
}

admin 更改状态以发布 2023年5月21日
0
0 Comments

确保不再保留你不需要的引用。

你仍然有对 alin 的引用。

尝试在调用垃圾回收之前添加 al = null; in = null;

另外,你需要意识到 substring 的实现方式。 substring 保留原始字符串,并仅使用不同的偏移和长度到相同的 char[] 数组。

al.add(new String(s.substring(0,1)));

不确定是否有更优雅的复制子字符串的方法。也许对你来说,s.getChars() 更有用。

从Java 8开始,substring现在会复制字符。你可以自行验证构造函数调用了 Arrays.copyOfRange

0
0 Comments

创建子字符串时,子字符串会引用原始字符串的字符数组(此优化使得处理字符串的许多子字符串非常快)。因此,当将子字符串存储在al列表中时,您将整个文件存储在内存中。为了避免这种情况,使用将字符串作为参数的构造函数创建新字符串。

所以基本上我建议你做

    while(in.hasNextLine()) {
        String s = in.nextLine();
        al.add(new String(s.substring(0,1))); // extracts first 1 character
    }

String(String)构造函数的源代码明确说明其用途是修剪“行李”:

  164       public String(String original) {
  165           int size = original.count;
  166           char[] originalValue = original.value;
  167           char[] v;
  168           if (originalValue.length > size) {
  169               // The array representing the String is bigger than the new
  170               // String itself.  Perhaps this constructor is being called
  171               // in order to trim the baggage, so make a copy of the array.
  172               int off = original.offset;
  173               v = Arrays.copyOfRange(originalValue, off, off+size);
  174           } else {
  175               // The array representing the String is the same
  176               // size as the String, so no point in making a copy.
  177               v = originalValue;
  178           }
  179           this.offset = 0;
  180           this.count = size;
  181           this.value = v;

更新:OpenJDK 7、更新6已经解决了这个问题。使用更高版本的人不会遇到这个问题。

0