Java不是垃圾收集内存

Java不是垃圾收集内存,java,performance,memory,garbage-collection,Java,Performance,Memory,Garbage Collection,我正在阅读一个非常大的文件,并从每行中提取一些小部分文本。然而,在手术结束时,我几乎没有什么内存可供使用。垃圾收集器在读取文件后似乎无法释放内存 我的问题是:有没有办法释放这个内存?或者这是一个JVM错误 我创建了一个SSCCE来演示这一点。它读入一个1MB(由于16位编码,在Java中为2MB)文件,并从每行(大约4000行,所以应该是8KB)中提取一个字符。在测试结束时,仍然使用完整的2MB 初始内存使用情况: Allocated: 93847.55 kb Free: 93357.23 kb

我正在阅读一个非常大的文件,并从每行中提取一些小部分文本。然而,在手术结束时,我几乎没有什么内存可供使用。垃圾收集器在读取文件后似乎无法释放内存

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

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

初始内存使用情况:

Allocated: 93847.55 kb
Free: 93357.23 kb
读取文件后(在任何手动垃圾收集之前):

这是意料之中的,因为程序正在使用大量资源读取文件

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

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);
    }
}
import java.io.*;
导入java.util.*;
公开课考试{
公共静态void main(字符串[]args)抛出可丢弃的{
Runtime rt=Runtime.getRuntime();
double alloc=rt.totalMemory()/1000.0;
double free=rt.freemory()/1000.0;
System.out.printf(“已分配:%.2f kb\n自由:%.2f kb\n\n”,分配,自由);
Scanner in=new Scanner(新文件(“my_File.txt”);
ArrayList al=新的ArrayList();
while(在.hasNextLine()中){
字符串s=in.nextLine();
al.add(s.substring(0,1));//提取前1个字符
}
alloc=rt.totalMemory()/1000.0;
free=rt.freemory()/1000.0;
System.out.printf(“已分配:%.2f kb\n自由:%.2f kb\n\n”,分配,自由);
in.close();
gc();
alloc=rt.totalMemory()/1000.0;
free=rt.freemory()/1000.0;
System.out.printf(“已分配:%.2f kb\n自由:%.2f kb\n\n”,分配,自由);
}
}

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

所以基本上我建议你这么做

    while(in.hasNextLine()) {
        String s = in.nextLine();
        al.add(new String(s.substring(0,1))); // extracts first 1 character
    }
String(String)构造函数的源代码明确声明其用途是修剪“行李”:

更新:OpenJDK 7、更新6解决了这个问题。使用较新版本的用户没有问题。

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

阅读更多文档


另一个关于它的问题可以在

上找到,请确保不要保留您不再需要的参考资料

您仍然有对
al
的引用

尝试添加
al=null;in=null

此外,您还需要了解如何实现
子字符串
子字符串
保留原始字符串,只是对相同的
char[]
数组使用不同的偏移量和长度。

al.add(new String(s.substring(0,1)));
不确定是否有更优雅的方式复制子字符串。也许
s.getChars()
对您也更有用


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

,除非您正在做一些其他人都不可能做的非常不寻常的事情,“jvm bug”不应该是您的第一个假设。您希望System.gc()如何释放所有内存?你仍然在使用al中的字符串,所以它们无法释放。@PaulTomblin:我已经研究这个问题有一段时间了,但什么都没有出现。“我看不出有什么好的理由来解释为什么会发生这种情况。”戴斯特罗伊的回答一针见血。子字符串不是生成一个全新的字符串,它保存了对原始字符串的引用。
substring
保留了原始字符数据。我需要使用
al
中的字符串。我只想对文件的其余部分进行垃圾收集。至少在我的JDK中,
newstring
将在长度不同时修剪字符串。Hm。。。有趣。这是一个奇怪的子字符串优化。但它解释了发生了什么。还有一个关于它的bug报告:我记得,这在java的第一个版本(1.02)中出现过,当时它被视为一个智能优化。问题是它使垃圾处理变得更加复杂。我可以看到它背后的原因,因为它将子字符串简化为
O(1)
操作。但对我来说,这几乎像是内存泄漏。@assylias请看一下字符串(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;
al.add(new String(s.substring(0,1)));