尝试处理 s3 文件时出现 OOM
OOM when trying to process s3 file
我正在尝试使用下面的代码从文件中下载和读取数据,无论如何 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);
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332) at
java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at
java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:596)
at java.lang.StringBuffer.append(StringBuffer.java:367) at
java.io.BufferedReader.readLine(BufferedReader.java:370) at
java.io.BufferedReader.readLine(BufferedReader.java:389) at
com.kpmg.rrf.utils.AWSUtils.unzip(AWSUtils.java:917)
这是一个理论,但我想不出您的示例会 OOM 的任何其他原因。
假设解压后的文件包含一行很长;例如大约 6.5 亿个 ASCII 字节。
您的应用程序似乎只是一次读取文件一行,并(尝试)显示已读取的总字节数 运行。
在内部,readLine()
方法一次读取一个字符并将它们附加到 StringBuffer
。 (您可以在堆栈跟踪中看到 append
调用。)如果文件包含非常大的行,那么 StringBuffer
会变得非常大。
未压缩字符串中的每个文本字符在 char[]
中成为 char
,即 StringBuffer
.
[ 的缓冲区部分=53=]
每次缓冲区填满时,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);
}
我也想到了排队太长。
转念一想,JVM内部使用的StringBuffer需要转换成readline的结果类型:String。字符串是不可变的,但出于速度原因,如果一行重复,JVM 甚至不会查找。所以它可能会多次分配 String,最终用不再使用的 String 片段填满堆。
我的建议是不要读取行或字符,而是字节块。一个 byte[] 分配在堆上,之后可以丢弃。当然你会计算字节而不是字符。除非您知道其中的区别并且需要更稳定、更高效的解决方案。
这段代码只是凭记忆写的,未经测试:
public static String unzip(InputStream in)
throws IOException, CompressorException, ArchiveException {
System.out.println("Unzipping.............");
try (
GZIPInputStream gzis = new GZIPInputStream(in);
) {
byte[] buffer = new byte[8192];
long i = 0;
int read = gzis.read(buffer);
while (read >= 0) {
i+=read;
if (i % (100 * 1024 * 1024) == 0) {
System.out.println(i / (1024 * 1024));
}
read = gzis.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
LOG.error("Invoked AWSUtils getS3Content : json ", e);
}```
我正在尝试使用下面的代码从文件中下载和读取数据,无论如何 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);
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:596) at java.lang.StringBuffer.append(StringBuffer.java:367) at java.io.BufferedReader.readLine(BufferedReader.java:370) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.kpmg.rrf.utils.AWSUtils.unzip(AWSUtils.java:917)
这是一个理论,但我想不出您的示例会 OOM 的任何其他原因。
假设解压后的文件包含一行很长;例如大约 6.5 亿个 ASCII 字节。
您的应用程序似乎只是一次读取文件一行,并(尝试)显示已读取的总字节数 运行。
在内部,readLine()
方法一次读取一个字符并将它们附加到 StringBuffer
。 (您可以在堆栈跟踪中看到 append
调用。)如果文件包含非常大的行,那么 StringBuffer
会变得非常大。
未压缩字符串中的每个文本字符在
[ 的缓冲区部分=53=]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);
}
我也想到了排队太长。 转念一想,JVM内部使用的StringBuffer需要转换成readline的结果类型:String。字符串是不可变的,但出于速度原因,如果一行重复,JVM 甚至不会查找。所以它可能会多次分配 String,最终用不再使用的 String 片段填满堆。
我的建议是不要读取行或字符,而是字节块。一个 byte[] 分配在堆上,之后可以丢弃。当然你会计算字节而不是字符。除非您知道其中的区别并且需要更稳定、更高效的解决方案。
这段代码只是凭记忆写的,未经测试:
public static String unzip(InputStream in)
throws IOException, CompressorException, ArchiveException {
System.out.println("Unzipping.............");
try (
GZIPInputStream gzis = new GZIPInputStream(in);
) {
byte[] buffer = new byte[8192];
long i = 0;
int read = gzis.read(buffer);
while (read >= 0) {
i+=read;
if (i % (100 * 1024 * 1024) == 0) {
System.out.println(i / (1024 * 1024));
}
read = gzis.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
LOG.error("Invoked AWSUtils getS3Content : json ", e);
}```