java - 尝试处理 s3 文件时出现 OOM
问题描述
我正在尝试使用下面的代码从文件中下载和读取数据,无论如何都会OOM,正是在读取文件时,s3文件的大小为22MB,我通过浏览器下载它是650 MB,但是当我通过视觉监控时VM,解压缩和读取时消耗的内存超过2GB。请任何人指导,以便我找到内存使用率高的原因。谢谢。
public static String unzip(InputStream in) throws IOException, CompressorException, ArchiveException {
System.out.println("Unzipping.............");
GZIPInputStream gzis = null;
try {
gzis = new GZIPInputStream(in);
InputStreamReader reader = new InputStreamReader(gzis);
BufferedReader br = new BufferedReader(reader);
double mb = 0;
String readed;
int i=0;
while ((readed = br.readLine()) != null) {
mb = mb+readed.getBytes().length / (1024*1024);
i++;
if(i%100==0) {System.out.println(mb);}
}
} catch (IOException e) {
e.printStackTrace();
LOG.error("Invoked AWSUtils getS3Content : json ", e);
} finally {
closeStreams(gzis, in);
}
线程“主”java.lang.OutOfMemoryError 中的异常:java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) 处 java.util.Arrays.copyOf(Arrays.java:3332) 处的 Java 堆空间。在 java.io.BufferedReader.readLine(BufferedReader.java:370) 在 java.io.BufferedReader.readLine(BufferedReader) 的 java.lang.StringBuffer.append(StringBuffer.java:367) 的 AbstractStringBuilder.append(AbstractStringBuilder.java:596) .java:389) 在 com.kpmg.rrf.utils.AWSUtils.unzip(AWSUtils.java:917)
解决方案
这是一个理论,但我想不出你的例子会OOM的任何其他原因。
假设未压缩的文件包含很长的一行;例如 6.5 亿个 ASCII 字节。
您的应用程序似乎只是一次读取文件一行并(尝试)显示已读取的运行总兆字节数。
在内部,该readLine()
方法一次读取一个字符并将它们附加到StringBuffer
. (您可以append
在堆栈跟踪中看到调用。)如果文件包含一个非常大的行,那么StringBuffer
将会变得非常大。
未压缩字符串中的每个文本字符都变成
char
.char[]
的缓冲区部分中的StringBuffer
.每次缓冲区填满时,
StringBuffer
缓冲区都会(我认为)增加一倍。这需要分配一个新char[]
的并将字符复制到它。因此,如果缓冲区在有 N 个字符时填满,
Arrays.copyOf
将分配一个char[]
保持 2 x N 个字符的空间。在复制数据时,总共将使用 3 x N 的字符存储空间。所以 650MB 很容易变成大于 6 x 650M 字节的堆需求
需要注意的另一件事是 2 x N 数组必须是单个连续堆节点。
查看堆图,看起来堆的使用量约为 1GB。如果我的理论是正确的,那么下一次分配将用于 ~2GB 节点。但是 1GB + 2GB 正好是 3.1GB 堆最大值的限制。而当我们考虑到连续性要求时,分配是无法完成的。
那么解决方案是什么?
readLine()
这真的很简单:如果行可能过长, 请不要使用。
public static String unzip(InputStream in)
throws IOException, CompressorException, ArchiveException {
System.out.println("Unzipping.............");
try (
GZIPInputStream gzis = new GZIPInputStream(in);
InputStreamReader reader = new InputStreamReader(gzis);
BufferedReader br = new BufferedReader(reader);
) {
int ch;
long i = 0;
while ((ch = br.read()) >= 0) {
i++;
if (i % (100 * 1024 * 1024) == 0) {
System.out.println(i / (1024 * 1024));
}
}
} catch (IOException e) {
e.printStackTrace();
LOG.error("Invoked AWSUtils getS3Content : json ", e);
}
推荐阅读
- node.js - 这里使用 NodeJS 映射 API 请求令牌
- html - G Suite 帐户的自定义 HTML 对话框中的 google.script.run 问题(但不是个人 Google 帐户)
- sql - 带有特殊字符(ñ 和重音)的 RTF 到 SQL
- asp.net-core - 有没有办法将任何 IDistributedCache 用作 .net 核心中的 ResponseCache?
- c# - 将 GetInt64/SetInt64 添加到 ASP.NET Core 会话
- python - 使用 Flask 上传文件时,我应该如何处理重复的文件名
- amazon-web-services - 如果在扫描时在 DynamoDB 中修改项目会发生什么
- javascript - 如何在父项更改时将道具传递给反应组件但不更新子项中的该道具?
- javascript - 我需要使用 javascript 在字符串中提取单词 CaseID- 之后的所有数字
- haskell - 在新闻合成器中使用 RandomGen