Java不是垃圾收集内存

时间:2012-06-08 15:32:16

标签: java performance memory garbage-collection

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

我的问题是:有没有办法释放这段记忆?或者这是一个JVM错误吗?

我创建了一个SSCCE来证明这一点。它读取1 mb(由于16位编码而在Java中为2 mb)文件并从每行中提取一个字符(~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<String> al = new ArrayList<String>();

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

3 个答案:

答案 0 :(得分:22)

在创建子字符串时,您的子字符串会保留对原始字符串的char数组的引用(此优化可以非常快速地处理字符串的许多子字符串)。因此,当您将子字符串保存在al列表中时,您将整个文件保留在内存中。要避免这种情况,请使用以字符串作为参数的构造函数创建一个新String。

基本上我建议你这样做

    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,Update 6已解决此问题。版本较新的用户没有问题。

答案 1 :(得分:6)

确保不再保留您不再需要的参考资料。

您仍然可以引用alin

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

此外,您需要了解substring的实施方式。 substring会保留原始字符串,并且只使用不同的偏移量和长度来使用相同的char[]数组。

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

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

从Java 8开始,子字符串 现在复制字符。您可以验证构造函数是否调用Arrays.copyOfRange

答案 2 :(得分:-1)

System.gc()不保证JVM将进行垃圾收集 - 它只是对JVM的建议,它可以尝试并进行垃圾收集。由于已经有大量内存可用,JVM可能会忽略建议并继续运行直到感觉需要这样做。

阅读文档http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#gc()

了解更多信息

When does System.gc() do anything

提供了另一个有关它的问题