URLConnection.getInputStream()使用太多内存

时间:2018-06-28 10:00:49

标签: java http download inputstream urlconnection

我需要在嵌入式系统上下载大量(也许> 5000)相对较小(小于千字节)的文件,所以我没有太多内存。

我已经编写了这段代码,用于下载每个文件(例如,仅提供一个文件)

final int BUFFER_LENGTH = 64 * 1024;

URL fileUrl = new URL("http://10.10.0.119:8080/files/a.txt");
File fileToSave = new File("/Users/me/foo/a.txt");

URLConnection connection = fileUrl.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);

InputStream us = connection.getInputStream(); // HOT SPOT (1)

try (BufferedInputStream bs = new BufferedInputStream(us, (int) Math.min(fileSize, 8192))) // HOT SPOT (2)
{
    try (FileOutputStream fs = new FileOutputStream(fileToSave))
    {
        int c;
        while ((c = bs.read(data, 0, BUFFER_LENGTH)) != -1)
            fs.write(data, 0, c);
    }
}

还要提及

private static final int BUFFER_LENGTH = 64 * 1024;
private final byte data[] = new byte[BUFFER_LENGTH]

每个“下载器”实例分配一次,例如一生一次。

因此,我注意到该代码使用了相对较高的内存(> 200Mb)(但是所有这些都被GC进一步成功释放了),我开始使用JProfiler进行性能分析。我注意到的是connection.getInputStream()在程序的生命周期内分配了大约120Mb,并且分配了BufferedInputStream(通过将其确切大小放入流构造函数中来减小大小来进行优化)。

这是我的分析结果。我启用了收集有关GCed对象的信息。您可能已经注意到,我提到的两个最重的热点是URLConnection.getInputStream()new BufferedInputStream()enter image description here

在这种情况下如何减少内存使用量?也许还有其他解决方案,例如:

  • 重复使用此类流
  • 明确指出尺寸
  • 使用一些不同的方法

非常感谢。

1 个答案:

答案 0 :(得分:2)

您的应用程序正在使用字节数组作为缓冲区进行读写。该可以分配一次,然后可用于所有文件。 (实际上,您可能已经在这样做了……尽管您没有向我们展示实际的代码。)

如果您使用大的byte[]作为缓冲区进行读取和写入(如您当前所做的那样),则无需使用BufferedInputStream。 (相对于显式使用缓冲区,使用BufferedInputStream不会提高性能。)由于每次创建新的BufferedInputStream时,它都会分配一个新的字节数组作为内部缓冲区,因此您会发现直接从InputStream(即us)可以节省内存,而且不会降低性能。


您的想法是:

  

重复使用此类流

您不能使用标准Java API来做到这一点。

  

明确指出尺寸

我假设您的意思是创建大小与输入流内容大小完全匹配的缓冲区。

  1. 如果您回收缓冲区(按照我的建议),那将无济于事

  2. 这可能还是无济于事。在基本级别上,您的代码将从套接字流中读取,并且读取通常不会填充缓冲区。 (从套接字读取将在一个read调用中传递本地TCP协议栈中当前可用的数据,而不是整个流内容。)

  3. 超过几个KB,增加缓冲区大小几乎不会带来性能好处。 (您现有的64 KB缓冲区大小可能无法帮助提高吞吐量。)