繁体   English   中英

即使将 JVM 堆空间增加到 8GB,仍然出现“OutOfMemoryError:Java 堆空间”异常

[英]Still getting "OutOfMemoryError: Java heap space" exception even when increasing the JVM Heap Space to 8GB

我在下面有一个截图代码可以将文件上传到另一个存储。 它适用于小于 1GB 数据的文件。 但是,它会暴露“OutOfMemoryError: Java heap space”异常,文件大小 > 1GB(我用 1.2GB 和 1.5GB 验证过)

搜索一些帖子后,我将 JVM 堆空间增加到 8GB。 但是,我仍然看到异常。

我的机器是 16GB RAM

JVM版本和Java Runtime Environment设置如下在此处输入图像描述

这是片段代码:

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)

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

请注意, 自 Java 7 以来,仅使用RandomAccessFile来获取FileChannel已过时。

您将文件的内容复制到ByteArrayOutputStream中。 完成此操作后, ByteArrayOutputStream将拥有一个至少与复制数据一样大的数组,但由于在需要增加内部容量时不知道最终大小,因此它可能高达所需大小的两倍。

然后,你调用它的toByteArray() ,它的文档说“创建一个新分配的字节数组。 ”。

因此,从toByteArray()返回后,您使用的堆 memory 是实际文件大小的两到三倍。

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

所以当用这种方式读取一个1.5GB的文件时,你可能很容易就为arrays这几个字节占用了4.5GB。嗯,这不是确切的事实,因为数组大小被限制在大约2GB,所以数组大小不会超过这个,但内部阵列仍为 2GB,新阵列仍为 1.5GB。

请注意,即使在复制到ByteArrayOutputStream时,分配的 memory 也可能更高。 在最坏的情况下,stream 可能已经拥有一个将近 1.5GB 的数组并且必须增加它,因此它会分配一个 ~2GB 的数组,复制数据,然后它可能会删除旧数组。 所以在增长数组的时候已经临时占用了3.5GB。

您应该重新检查您正在使用的 API,它们是否真的要求您传递一个字节数组,将整个文件保存在堆 memory 中,而不是例如要从中复制的通道或ByteBuffer ,它可以是 memory 映射文件,而不是封装一个大批。

如果那不可能,至少要消除多余的副本。

您可以省略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()));

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM