URLConnection.getInputStream() 使用太多内存

URLConnection.getInputStream() uses too much memory

我需要在嵌入式系统上下载很多(可能>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]

每个 'downloader' 实例分配一次,例如一生一次。

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

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

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

非常感谢。

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

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


您的想法是:

Reusing such streams

标准 Java API 无法做到这一点。

Explicitly pointing the size

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

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

  2. 反正也没什么用。在基本级别,您的代码将从套接字流中读取,并且读取通常不会填充缓冲区。 (从套接字读取将传送本地 TCP 协议栈中当前可用的数据......而不是整个流内容......在一次 read 调用中。)

  3. 超过几 KB,增加缓冲区大小几乎没有性能优势。 (您现有的 64 KB 缓冲区大小可能对吞吐量没有帮助。)