我在下面有一段代码可以上传一个文件到另一个存储器。它可以正常处理小于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)
如果您能给予我一些建议,我将不胜感激!
1条答案
按热度按时间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
,将文件读入其中,并使用其字节数组。或者仅使用内置功能
虽然这在阅读时可能不使用锁定。