首页 > 解决方案 > 尝试处理 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)

监控

标签: javaamazon-web-servicesamazon-s3aws-lambdagzipinputstream

解决方案


这是一个理论,但我想不出你的例子会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);
        }

推荐阅读