URLConnection.getInputStream () использует слишком много памяти - PullRequest
0 голосов
/ 28 июня 2018

Мне требуется загрузить много (может быть> 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]

Распределяется один раз для экземпляра загрузчика, например один раз в жизни.

Итак, я заметил, что этот код использует относительно большой (> 200 МБ) объем памяти (но все это успешно освобождается GC), и я начал профилирование с использованием своего JProfiler. Что я заметил, так это то, что connection.getInputStream() выделяет около 120 МБ в течение срока службы моей программы, а также выделяет BufferedInputStream (что я оптимизировал, уменьшив его размер, поместив точный размер в конструктор потока).

Вот мои результаты профилирования. Я включил сбор информации об объектах GCed. Как вы можете заметить, двумя самыми тяжелыми горячими точками являются URLConnection.getInputStream() и new BufferedInputStream(), о которых я упоминал. enter image description here

Как я могу уменьшить использование памяти при таких обстоятельствах? Может быть, есть и другие решения, такие как:

  • Повторное использование таких потоков
  • Явно указывая размер
  • Используя несколько разных подходов

Большое спасибо.

1 Ответ

0 голосов
/ 28 июня 2018

Ваше приложение читает и пишет, используя байтовый массив в качестве буфера. Этот может быть выделен один раз и повторно использован для всех файлов. (На самом деле, вы, вероятно, уже делаете это ... хотя вы не показали нам реальный код.)

Если вы читаете и пишете, используя большой byte[] в качестве буфера (как вы сейчас делаете), тогда нет необходимости использовать BufferedInputStream. (Использование BufferedInputStream не улучшит производительность относительно явного использования буфера.) И поскольку каждый раз, когда вы создаете новый BufferedInputStream, он выделяет новый байтовый массив в качестве внутреннего буфера, вы обнаружите, что чтение непосредственно из InputStream (т. Е. us) должно сэкономить память и не стоить вам никакой производительности.


Ваши идеи были:

Повторное использование таких потоков

Вы не можете сделать это со стандартными API Java.

Явно указывая размер

Я предполагаю, что вы имеете в виду создание буферов, размер которых точно соответствует размеру содержимого входных потоков.

  1. Это не поможет, если вы перезапустите буфер (как я и предлагал)

  2. Вероятно, это все равно не поможет. На базовом уровне ваш код будет читать из потока сокетов, и чтение обычно не заполняет буфер в любом случае. (Чтение из сокета доставит данные, которые в настоящее время доступны в локальном стеке протокола TCP ... не все содержимое потока ... в один read вызов`.)

  3. Помимо нескольких килобайт, увеличение размера буфера дает мало выигрыша в производительности. (Ваш существующий размер буфера 64 КБ, вероятно, не помогает пропускной способности.)

...