【发布时间】:2019-02-15 10:21:27
【问题描述】:
当我有这样的课程结构时,我最终会遇到 OutOfMemory 错误。
DataHandler由8个线程的固定池调用(没有外部线程池管理。固定池创建一次,执行一次,如果一个线程死了,就不再创建新线程)。一次,多个线程调用 DataHandler,但由于 byteArrayOutputStreamBuffer 是 threadLocal,每个线程都有自己的本地缓冲区。每个线程获取数据,调用 HandleData(),一旦完成,重复循环。
传递的数据大小为 2 GB。因此,预期占用的总内存最多为(2 GB + 字节数组流的大小)* 线程数。数组流的最大大小应为 4 GB(由于调整内存大小而导致数据的两倍)。因此,预期的总堆将为 6*8 = 48 GB。堆被配置为处理更多(我已经尝试了高达 300 GB),但这个问题仍然存在。
public class DataHandler {
private static ThreadLocal<ByteArrayOutputStream> byteArrayOutputStreamBuffer =
new ByteArrayOutputStream();
void HandleData(byte[] data) {
ByteArrayOutputStream byteArrayOutputStream = byteArrayOutputStreamBuffer.get();
File tempFile = new File(getFileName());
try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile)) {
byteArrayOutputStream.write(data);
fileOutputStream.write(byteArrayOutputStream.toByteArray());
} finally {
byteArrayOutputStream.reset();
}
}
}
如果我删除了中间的 ByteArrayOutputStream,那么就没有 OOM。我正在尝试解释为什么 ByteArrayOutputStream 会导致 OOM。
编辑:我看到 toByteArray() 还会增加 2 GB,所以总数为 64 GB。
【问题讨论】:
-
你有什么邪恶的计算机,它有 300GB 的内存供 JVM 使用?我是否可以建议流式传输数据而不是将整个数据存储在字节数组中?
-
像往常一样,
Threadlocal是一种代码味道99.9999999%的时间,因为这个人不明白它做什么、它是如何做的以及什么时候应该或不应该做的时间百分比使用。这也不是当时的0.00000001%。 -
据我了解,如果线程管理不透明,
threadlocal会很危险。在这里,我只是创建了 8 个线程,它们永远不会重新创建或替换。我做了一个堆转储,我看到这 8 个线程中的每一个都“拥有” byteArrayOutputStreamBuffer 并占用了大量内存。即使 ByteArrayOutputStream 永远不会被垃圾回收,我也不认为它会超出大小这么多,因为我们重用了相同的缓冲区。 -
@user2478398 一些 EC2 实例提供高达 500 GB 的内存。
-
Runtime.getRuntime().maxMemory();为您的应用程序返回什么?
标签: java out-of-memory thread-local