Java不是垃圾收集内存

标签 java performance memory garbage-collection

我正在阅读一个非常大的文件并从每一行中提取一些文本的一小部分。然而,在手术结束时,我只剩下很少的内存可以使用了。似乎垃圾收集器在读入文件后无法释放内存。

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

我创建了一个 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<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);
    }
}

最佳答案

在创建子字符串时,您的子字符串会保留对原始字符串的 char 数组的引用(这种优化可以非常快速地处理字符串的多个子字符串)。因此,当您将子字符串保存在 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 中消失了。使用更新版本的人没有这个问题。

关于Java不是垃圾收集内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10951812/

相关文章:

java - 使用 Java 将文件复制到具有相同文件名的文件夹中

java - 如何用Java调用一个新的记事本并将其保存在windows中的目录中?

mysql - 如果不满足查询中的条件,mysql 是否会移动到下一行?

c# - 如何读取日志文件的最后 "n"行

python - Keras tf 后端预测批量大小为 1 时速度较慢

java - PCA pca = 新PCA

java - 用Java解析这个字符串

c - 使用 malloc、memtest 和 dd 了解 RAM 速度

C++ - 获取特定内存地址的值

ios - 从数据库存储和检索图像时的内存问题