【问题标题】:Java String Memory LeakJava 字符串内存泄漏
【发布时间】:2011-08-29 15:39:48
【问题描述】:

我不是 java 专家。

我的代码正在将文件读入String。此代码每 5 分钟执行一次。 文件大小不同。有时是 100 有时是 1000 行。

几天后,我经历了内存不足。

我的问题是,当我的代码超出Reading file function 的范围时,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();
    }
}

【问题讨论】:

  • 视情况而定。字符串是对象。对于对象,这取决于对象是否被任何东西引用(例如方法的返回值)。可以发一些代码吗?
  • 很高兴看到代码,以及你对读入的字符串做了什么。试着给我们一些SSCCE
  • 我看不出你发布的代码可以运行几天。
  • 变量s 在他检查它的地方永远不会为空,因为他向它添加了“abcd”。我以为它会抛出异常,但显然它变成了“nullabcd”
  • @Anonymoose:我不明白它怎么不能无限期运行 :)

标签: java memory memory-leaks garbage-collection out-of-memory


【解决方案1】:

你好,我不是java专家。

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

我的代码正在将文件读入字符串,此代码每 5 分钟执行一次。现在有时文件大小为 100 行,有时为 1000 行。

听起来不是很大或很频繁。应该没有问题。

几天后,我经历了内存不足。

您应该能够获得堆转储并查看内存不足的位置以及原因。

我的问题是,当我的代码超出读取文件功能的范围时。 Java Garbage 是否收集字符串。

当它不再通过强引用访问时,可以收集它。

在网上看到有人说它不会被删除并使用 StringBuffer,我很困惑

听起来您来对地方了。没听说过。

【讨论】:

    【解决方案2】:

    您的read 方法永远不会终止。到达文件末尾后,您只需将字符串 "nullabcd" 永远添加到 s

    编辑:忘记了,s 每次都会重新分配。不过,我看不出您的 read 方法如何终止。

    【讨论】:

    • 是的,我有一种强烈的感觉,发布的代码不是有问题的实际代码。
    【解决方案3】:

    您发布的代码不会泄漏内存。但是,while (true) 循环永远不会终止,因为在您对其进行测试时,s 永远不会是 null


    让我们稍微改变一下,让它“工作”

        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 是个好主意,但不是因为内存泄漏。如果您有内存泄漏,则不在原始示例代码中或在修复版本中。

    【讨论】:

      【解决方案4】:

      如下更改程序以减少内存消耗。内存消耗的巨大来源是由于您重复连接 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");
      }
      

      【讨论】:

        【解决方案5】:

        正如其他人指出的那样,此代码永远不会终止。您发布的代码似乎不是您遇到问题的原始代码。

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

        大胆猜测:完成后,您是否在 Readers 和 InputStreams 上调用 close()?如果不是,这可能是您的内存不足错误的原因。

        【讨论】:

          猜你喜欢
          • 2013-12-03
          • 2011-08-29
          • 2014-04-30
          • 2013-04-03
          • 1970-01-01
          • 2011-06-12
          • 2018-12-17
          • 2011-05-04
          • 1970-01-01
          相关资源
          最近更新 更多