Java不是垃圾收集内存

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

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

我创建了一个SSCCE来certificate这一点。 它读取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 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); } } 

在创建子字符串时, 您的子字符串会保留对原始字符串的char数组的引用 (此优化可以非常快速地处理字符串的许多子字符串)。 因此,当您将子字符串保留在列表中时,您将整个文件保留在内存中。 要避免这种情况,请使用以字符串作为参数的构造函数创建新的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已解决此问题。具有更新版本的人没有问题。

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

您仍然可以参考alin

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

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

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

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

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

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

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

关于它的另一个问题可以在什么时候使用System.gc()做什么