Java字符串内存泄漏

7
我不是Java专家。
我的代码将文件读入一个字符串。这段代码每5分钟执行一次。 文件的大小不同。有时是100行,有时是1000行。
几天后,我遇到了内存不足的问题。
我的问题是,当我的代码超出了“读取文件函数”的范围时,Java是否会回收字符串?
我在网上阅读了很多信息,感到非常困惑。有些人说它不会被删除,建议使用StringBuffer。
// Demonstrate FileReader.

import java.io.*;
class FileReaderDemo {
    public static void read(BufferedReader br) throws Exception {
        long length = 0;
        String s;
        while (true) {
            s = br.readLine();
            s += "abcd";
            if (s == null) {
                break;
            }
            length += s.length();
            //System.out.println(s);
        }
        System.out.println("Read: " + (length / 1024 / 1024) + " MB");
    }

    public static void main(String args[]) throws Exception {
        //FileReader fr = new FileReader("FileReaderDemo.java");
        FileReader fr = new FileReader("big_file.txt.1");
        BufferedReader br = new BufferedReader(fr);
        String s;
        read(br);
        fr = new FileReader("big_file.txt.1");
        br = new BufferedReader(fr);
        read(br);
        fr = new FileReader("big_file.txt.1");
        br = new BufferedReader(fr);
        read(br);
        fr = new FileReader("big_file.txt.1");
        br = new BufferedReader(fr);
        read(br);
        BufferedReader in = new BufferedReader(new InputStreamReader(System. in )); in .readLine();
        fr.close();
    }
}

4
这要看情况。字符串是对象。对于对象,关键在于该对象是否被任何东西引用(例如方法的返回值)。您能发一些代码吗? - Robin Green
2
很好看到代码,以及您读取的字符串的处理方式。请尽量提供一些SSCCE - Boro
1
我看不出来你发布的代码怎么可能运行几天。 - Anonymoose
1
变量s在检查它时从未为null,因为他将“abcd”添加到它。我以为它会抛出异常,但显然它变成了“nullabcd”。 - DHall
@Anonymoose:我不明白它怎么可能不会无限运行 :) - Grodriguez
5个回答

6

你好,我不是Java专家。

每个人都有可以学习的东西。

我的代码将文件读入字符串中,此代码每5分钟执行一次。有时100行,有时1000行。

听起来不是很大,也不是很频繁。不应该有问题。

我经常会遇到内存不足的情况。

你应该能够获取堆转储并查看您在哪里耗尽了内存以及原因。

我的问题是,当我的代码超出了读取文件函数的范围时,Java是否会进行垃圾回收?

当通过强引用无法再访问String对象时,它可以被收集。

我在网上看到的某些资料说它不会被删除,要使用StringBuffer。

听起来你来对地方了。我从来没有听说过这种说法。


5
您的read方法将永远不会终止。一旦您到达文件的末尾,您就会无限期地继续向s中添加字符串"nullabcd"
编辑:请忘记上面说的,因为每次都重新分配了s。但是,我仍然看不出您的read方法如何终止。

是的,我强烈感觉到发布的代码并不是真正出现问题的代码。 - Anonymoose

4
你发布的代码不会泄漏内存。然而,由于测试点的s永远不会是null,因此while (true)循环永远不会终止。
让我们稍微更改一下代码以使其“工作”。
    public static void read(BufferedReader br) throws Exception {
            long length = 0;
            String s = "";
            while (true) {
                    String ss = br.readLine();
                    if (ss == null) {
                            break;
                    }
                    s += ss;
                    length += ss.length();
            }
            System.out.println("Read: " + (length/1024/1024) + " MB");
    }

这段代码不会泄漏内存,因为方法中创建的字符串在方法返回时(如果没有先释放)都将成为垃圾回收的候选对象。
每次执行 s += ss; 时,都会创建一个新的字符串,其中包含当前 s 中的所有字符和 ss 中的字符。假设有 N 行,平均包含 L 个字符,则 s += ss; 语句将被调用 N 次,将创建 N 个字符串,并且平均将复制 (N * L)^2 / 2 个字符。
然而,确实有一个很好的理由使用 StringBuilder,那就是减少字符串分配和字符复制的数量。让我们重写这个方法,使用 StringBuilder;即用于替代未同步的 StringBuffer
    public static void read(BufferedReader br) throws Exception {
            long length = 0;
            StringBuilder sb = new StringBuilder(sb);
            while (true) {
                    String ss = br.readLine();
                    if (ss == null) {
                            break;
                    }
                    sb.append(ss);
                    length += ss.length();
            }
            System.out.println("Read: " + (length/1024/1024) + " MB");
    }

这个版本会重新分配StringBuilder的内部字符数组,最多log2(N)次,并且最多复制2 * N * L个字符。


总结 - 使用StringBuilder是一个好主意,但不是因为内存泄漏。如果您有内存泄漏,它不在原始示例代码或修复后的版本中。


2

将程序更改如下以减少内存消耗。一个巨大的内存消耗源是由于您重复的字符串拼接s += "abcd"; - 避免这种情况,您可能会将内存消耗减少一半以上(未经测试-如果您想知道,请自行分析)。

public static void read(BufferedReader br) throws Exception {

    long length = 0;
    //String s; <--- change to the line below
    StringBuilder sb = new StringBuilder();
    while (true) {
        String s = br.readLine();
        if (s == null) {
            break;
        }
        //s += "abcd";  <--- change to the line below
        sb.append(s).append("abcd");
        length += s.length();
        //System.out.println(s);
    }
    System.out.println("Read: " + (length / 1024 / 1024) + " MB");
}

1

正如其他人指出的那样,这段代码永远不会终止。看起来你发布的代码不是你遇到问题的原始代码。

没有看到实际代码很难诊断,但是一旦字符串不再从代码的其他部分引用,它们肯定会被垃圾回收。

猜测:你在完成读取器和输入流后是否调用了close()?如果没有,这可能是你内存不足错误的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接