仍然收到“内存不足错误:即使将JVM堆空间增加到8GB,也会出现“Java堆空间”异常错误

sigwle7e  于 2022-11-07  发布在  Java
关注(0)|答案(1)|浏览(144)

我在下面有一段代码可以上传一个文件到另一个存储器。它可以正常处理小于1GB的数据。但是,它会暴露“OutOfMemoryError:Java堆空间”异常,文件大小〉1GB(我验证了1.2GB和1.5GB)
在搜索了一些帖子之后,我将JVM堆空间增加到了8 GB。但是,我仍然看到了异常。
我的机器有16 GB的内存
JVM版本和Java Runtime Environment设置如下x1c 0d1x
下面是代码片段:

protected boolean uploadFile(File file) throws BaseAFException {
    try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
         FileChannel channel = randomAccessFile.getChannel();
         FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {

        PayloadMessage payload = new PayloadMessage();

        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            int bufferSize = 1024;
            if (bufferSize > channel.size()) {
                bufferSize = (int) channel.size();
            }
            ByteBuffer buff = ByteBuffer.allocate(bufferSize);
            while (channel.read(buff) > 0) {
                out.write(buff.array(), 0, buff.position());
                buff.clear();
            }

            //the exception exposes before the debugger hits the line below         
            payload.setContent(out.toByteArray()); 
        }

        //Upload file will be handled here

        return true;
    } catch (OverlappingFileLockException e) {      
        return false;
    } catch (Exception e) {
        throw new Exception("Could not upload file " + file.getAbsolutePath(), e);
    }
}

异常错误堆栈跟踪:

Throwable : java.lang.OutOfMemoryError: Java heap space java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Java heap space
    at java.util.concurrent.FutureTask.report(Unknown Source)
    at java.util.concurrent.FutureTask.get(Unknown Source)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.io.ByteArrayOutputStream.grow(Unknown Source)
    at java.io.ByteArrayOutputStream.ensureCapacity(Unknown Source)
    at java.io.ByteArrayOutputStream.write(Unknown Source)

如果您能给予我一些建议,我将不胜感激!

siv3szwd

siv3szwd1#

请注意,使用RandomAccessFile来获取FileChannel在Java 7之后已经过时。
将文件内容复制到ByteArrayOutputStream中,完成此操作后,ByteArrayOutputStream将拥有至少与复制数据一样大的数组,但由于在需要增加内部容量时不知道最终大小,因此最多可能是所需大小的两倍。
然后,在其上调用toByteArray(),其文档显示“* 创建新分配的字节数组。*"。
因此,从toByteArray()返回后,您使用的堆内存是实际文件大小的两到三倍。

  • ByteArrayOutputStream保存整个文件,但容量可能高达所需容量的两倍
  • toByteArray()返回的与文件大小匹配的数组

因此,当阅读一个1.5GB的文件时,这些字节数组可能会占用4.5GB的空间。实际情况并非如此,因为数组大小被限制在2GB左右,所以数组大小不会超过这个值,但内部数组仍然是2GB,而新数组则是1.5GB。
请注意,即使在复制到ByteArrayOutputStream时,分配的内存可能会更高。在最坏的情况下,流可能已经拥有了一个大约1.5GB的数组,必须增加它,因此它将分配一个约2GB的数组,复制数据,只有在之后它才可能删除旧的数组。因此,在增加数组时,它将临时占用3.5GB。
你应该重新检查你正在使用的API,它们是否真的需要你传递一个字节数组来保存堆内存中的整个文件,而不是一个通道来复制,或者一个ByteBuffer,它可能是一个内存Map文件,而不是封装一个数组。
如果这不可能,至少消除多余的副本。
您可以省略ByteArrayOutputStream,只使用文件大小的ByteBuffer,将文件读入其中,并使用其字节数组。

protected boolean uploadFile(File file) throws BaseAFException {
    PayloadMessage payload = new PayloadMessage();

    try(FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ);
        FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {

        long size = channel.size();
        if(size > Integer.MAX_VALUE) {
            throw new Exception("can't keep " + size + " bytes in an array");
        }

        ByteBuffer buff = ByteBuffer.allocate((int)size);
        do channel.read(buff); while(buff.hasRemaining());

        payload.setContent(buff.array());
    }
    catch( … ) …

    // handle upload HERE when resources have been closed
    // to have at least one advantage of reading the entire file into memory
}

或者仅使用内置功能

PayloadMessage payload = new PayloadMessage();
payload.setContent(Files.readAllBytes(file.toPath()));

虽然这在阅读时可能不使用锁定。

相关问题