出现内存不足异常的根本原因是什么?我们如何克服这一点?

What is the root cause of getting Out Of Memory Exception? How can we overcome this?

这是我通过输出流读写的示例片段,我遇到了内存不足异常。

 public static void readFileContent(InputStream in, OutputStream out) throws IOException {
    byte[] buf = new byte[500000];
    int nread;
    int navailable;
    int total = 0;
    synchronized (in) {
        try {
            while((nread = in.read(buf, 0, buf.length)) >= 0) {
                out.write(buf, 0, nread);
                total += nread;
            }
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    out.flush();
    buf = null;
}
  1. What are the possible scenarios with the above snippet to get "out of memory exception" ?
  2. Is it necessary to close the output stream here? And does stream, flush is enough or do we need to close the stream always? If so why?
  3. How could I avoid Out of memory exception in general?

请澄清一下。

在 Java 中没有任何释放内存的粗暴方法。即使调用内置垃圾收集器 (System.gC()) 也可能无法解决问题,因为 GC 只会释放不再被引用的对象。您需要照顾好您正在编写的代码,以便它可以以最佳方式利用资源。当然,在某些情况下,您别无选择,尤其是当您使用大型或巨型数据结构时,无论您能想到任何代码优化(在您的情况下,您正在创建一个包含 50 万字节记录的数组) .

作为部分解决方案,您可以增加堆大小内存,以便 Java 可以分配更多内存。

我认为问题很明显,您一次分配了 500000 个字节,它们在运行时可能在堆中不可用。

解释: 我不建议这样做,但您可以增加程序的堆大小。 java 程序的默认堆大小为 determined at runtime,但也可以参数化。

建议: 据我所提供的片段所见,一次读取 500000 字节并不是绝对必要的。因此,您可以使用较小的数字初始化字节数组,这会导致更多的读取循环。但如果这对你的程序来说不是问题......我想。

结论: 尝试将初始字节数组大小设置为 5000,甚至 1000.

编辑:

需要额外考虑的一点是,在上面的代码片段中,您最后只刷新了一次。您写入 OutputStream 的字节保存在内存中,它们的大小也可能导致 OutOfMemoryException

为了克服这个问题,您应该更频繁地冲洗。如果你经常刷新它会影响你的表现,但你总是可以在你的循环中试验一个条件,例如

...
if (total % 5000 == 0) {
    out.flush();
}
...

编辑 2:

由于 InputStreamOutputStream 对象作为参数传递给给定的方法,因此,在我看来,该方法不负责关闭它们。初始化 Streams 的方法也负责优雅地关闭它们。 Flush 这个方法就够了。但考虑分小块进行。

编辑 3:

总结建议的调整:

public static void readFileContent(InputStream in, OutputStream out) throws IOException {
    byte[] buf = new byte[1000];
    // wrap your OutputStream in a BufferedOutputStream
    BufferedOutputStream bos = new BufferedOutputStream(out, 5000);
    int nread;
    int navailable;
    int total = 0;
    synchronized (in) {
        try {
            while((nread = in.read(buf, 0, buf.length)) >= 0) {
                // use the BufferedOutputStream to write data
                // you don't need to flush regularly as it is handled automatically every time the buffer is full
                bos.write(buf, 0, nread);
                total += nread;
            }
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // flush the last contents of the BufferedOutputStream
    bos.flush();
    buf = null;
}

另请注意,BufferedOutputStream 将在您正常关闭它时自动调用 flush()

编辑 4:

调用上述方法的示例:

public static void main(String[] args) {
    String filename = "test.txt";
    String newFilename = "newtest.txt";

    File file = new File(filename);
    File newFile = new File(newFilename);

    try (InputStream fis = new FileInputStream(file);
            OutputStream fout = new FileOutputStream(newFile)) {
        readFileContent(fis, fout);
    }
    catch(IOException ioe) {
        System.out.println(ioe.getMessage());
    }
}
  1. 将buf更改为新字节[1*1024]
  2. 只使用 buf 读取,无需指定长度,例如pos = in.read(buf)

其余代码看起来不错。不需要增加内存。 另外,同步inputStream有什么要点?

  1. What are the possible scenarios with the above snippet to get "out of memory exception" ?

内存不足异常的根本原因有多种。有关详细信息,请参阅 oracle 文档 page

java.lang.OutOfMemoryError: Java heap space:

原因:详细消息Java堆space表示无法在Java堆中分配对象。

java.lang.OutOfMemoryError: GC Overhead limit exceeded:

原因:详细消息"GC overhead limit exceeded"表明垃圾收集器一直在运行并且Java程序变得非常慢进度

java.lang.OutOfMemoryError: Requested array size exceeds VM limit:

原因:详细消息"Requested array size exceeds VM limit" 表明应用程序(或该应用程序使用的 API)试图分配一个大于堆大小的数组。

java.lang.OutOfMemoryError: Metaspace:

原因:Javaclass元数据(Javaclass的虚拟机内部表示)在本机分配内存(这里指metaspace)

java.lang.OutOfMemoryError: request size bytes for reason. Out of swap space?:

原因: 详细消息 "request size bytes for reason. Out of swap space?" 似乎是一个 OutOfMemoryError 异常。然而,Java HotSpot VM 代码在从本机堆分配失败并且本机堆可能接近耗尽时报告这个明显的异常

  1. Is it necessary to close the output stream here? And does stream, flush is enough or do we need to close the stream always? If so why?

由于您在方法中使用原始 InputStreamOutputStream,我们不知道传递给此方法的实际流是哪种类型,因此明确关闭这些流是个好主意.

  1. How could I avoid Out of memory exception in general?

这个问题已经回答了你的第一个问题。

参考这个关于为 IO 操作处理大文件的 SE 问题:

Java OutOfMemoryError in reading a large text file