Управление памятью, выделенной при загрузке ZipFile

Я пытаюсь загрузить 69 930 файлов в основной текстовый редактор. Все идет гладко, и после того, как все они загружены, объем памяти составляет очень прохладные 130 МБ. Однако во время пикового времени загрузки он может достигать 900–1200 МБ.

Вся память ссылается на _ 1_. Это используется только для загрузки файла в объектную модель, затем он больше никогда не используется и байты могут быть очищены.

Очевидно, вся дополнительная память очищается сборщиком мусора вскоре после загрузки, поэтому утечек памяти нет. Однако кажется излишним использовать столько дополнительной памяти.

Что я пробовал:

  1. Проблема с памятью «решается» путем System.gc() вызова сразу после закрытия ZipFile. Это приводит к ~ 75% времени наблюдения за потоками, высокой загрузке ЦП и медленной загрузке.
  2. Уменьшение количества потоков в пуле. Это снизило нагрузку (до 300 МБ), но привело к значительному увеличению времени загрузки.
  3. WeakReference

Что у меня есть на данный момент:

Анализ размера кучи

Я вызываю нагрузку через пул потоков с 4 потоками, каждый из которых выполняет относительно простую задачу:

// Source source = ...;
final InputStream input = source.open();

// read into object model

input.close();

Source в данном случае - это ZipFileSource, который выполняет все чтение:

import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class ZipFileSource implements Source {

    private final String file;
    private final String name;

    private volatile ZipFile zip;

    public ZipFileSource(final String file, final String name) {
        this.file = file;
        this.name = name;
    }

    @Override
    public InputStream open() throws IOException {
        close();

        final ZipFile zipFile = new ZipFile(file);
        final ZipEntry entry = zipFile.getEntry(name);

        final InputStream stream = new ZipFileSourceZipInputStream(zipFile.getInputStream(entry));

        this.zip = zipFile;

        return stream;
    }

    @Override    
    public void close() throws IOException {
        if (zip != null) {
            zip.close();
            zip = null;
        }
    }

    private class ZipFileSourceZipInputStream extends InputStream {

        private final InputStream stream;

        ZipFileSourceZipInputStream(final InputStream stream) {
            this.stream = stream;
        }

        @Override
        public int read() throws IOException {
            return stream.read();
        }

        @Override
        public void close() throws IOException {
            ZipFileSource.this.close();
            stream.close();
        }
    }
}

У меня мало идей. Я дошел до того, что либо использую собственный распаковщик zip, блокируя каждый n запрос на выполнение System.gc() вызова, либо просто отказываюсь и позволяю ему делать свое дело.

Есть ли способ более эффективно управлять памятью до того, как она накапливается (требуется вызов сборки мусора)?


person Obicere    schedule 29.03.2016    source источник
comment
Во-первых, создайте zip-файл меньшего размера :)   -  person user218867    schedule 29.03.2016
comment
@EricWang Я загружаю всю имеющуюся у меня библиотеку Eclipse. Потому что ... почему бы и нет?   -  person Obicere    schedule 29.03.2016
comment
Вы просто пытались заставить jvm использовать меньше памяти при запуске программы? Или ?   -  person user218867    schedule 29.03.2016
comment
@EricWang Я пытаюсь «сгладить» этот всплеск памяти, который происходит во время загрузки файла. После этого нет утечек или проблем с памятью.   -  person Obicere    schedule 29.03.2016
comment
На мой взгляд, в этом нет необходимости. JVM не очищает кучу сразу после того, как часть памяти не используется, для повышения производительности. Вы можете видеть из вашего графика, что minor gc или full gc выполняется jvm 4 раза автоматически, чтобы очистить молодое поколение или старое поколение во время загрузки файла. Затем вы явно вызвали full gc. Это не утечка памяти. Сборщик мусора повлияет на нормальное обслуживание программы Java, поэтому он откладывается до тех пор, пока это необходимо (до того, как закончится память). Вы можете добавить больше памяти и выделить ее процессу Java, если это действительно необходимо.   -  person user218867    schedule 29.03.2016
comment
@EricWang Я просто повозился с некоторыми вещами и получил это. Едва увеличилось время загрузки, но вы можете увидеть огромную разницу в шипах. Однако я задал этот вопрос, чтобы увидеть, есть ли более подходящее решение, чем static int, оператор остатка и System.gc() вызов. Честно говоря, просто для того, чтобы сделать процесс более плавным, а не для решения каких-либо проблем (например, утечек памяти). Так что, на мой взгляд, это изменение необходимо.   -  person Obicere    schedule 29.03.2016
comment
Если ваши zip-файлы разделены на множество небольших zip-файлов, вы можете: 1) Если вам действительно нужно контролировать память, используемую jvm, тогда установите для -Xmx небольшое значение, компромисс в том, что gc будет вызываться чаще, это я полагаю, не годится для вашей службы. 2) Если вы хотите, чтобы программа выполнялась очень быстро, выделите больше памяти также через -Xmx, чтобы gc вызывался реже, и запускайте n потоков параллельно, где n = количество ваших процессоров.   -  person user218867    schedule 29.03.2016
comment
На первый взгляд, полученный результат не сильно отличается от времени. Но в некоторой системе курсовых заданий в реальном времени (например, в системе торговли акциями) это может быть смертельно опасным. Когда вы выполняете сборку мусора, особенно небольшую сборку мусора, задержка обслуживания (в течение этого короткого периода) может привести к большим финансовым потерям только из-за нескольких сотен миллисекунд.   -  person user218867    schedule 29.03.2016
comment
Возможно, попробуйте какой-нибудь буфер, чтобы загрузить более мелкие фрагменты ZIP для работы. Если вы хотите загрузить весь ZIP-файл в память, то поведение, которое вы показали, является ожидаемым.   -  person Jire    schedule 29.03.2016
comment
@Jire насчитывается 576 индивидуальных банок. Самый большой, который я увидел при быстром сканировании, был около 4 МБ. Я бы сказал, что достаточно мелких деталей предостаточно.   -  person Obicere    schedule 29.03.2016
comment
Возможный дубликат Java VM - возвращается ли освобожденная память в ОС?   -  person the8472    schedule 30.03.2016
comment
@ the8472 Я изменил свой вопрос, чтобы прояснить, почему это не дубликат, поскольку это пытается решить проблему после того, как она была создана. Этот вопрос больше о том, чтобы вообще не позволять памяти накапливаться.   -  person Obicere    schedule 30.03.2016
comment
@Obicere, это то же самое. Когда происходит крупный сборщик мусора, он удаляет все собираемые объекты, а затем соответствующим образом изменяет размер кучи, допуская столько потерь (что есть по какой-то причине), как указано в этих параметрах. Чего еще вы ожидаете? Вы не можете освободить память, на которую все еще ссылаются.   -  person the8472    schedule 31.03.2016
comment
См. Также stackoverflow.com/questions / 1481178 /   -  person Raedwald    schedule 31.03.2016
comment
Еще раз подчеркну, что эту проблему не нужно решать с помощью сборки мусора, и, вероятно, не стоит.   -  person Obicere    schedule 31.03.2016


Ответы (1)


A) если ваше приложение продолжает работать, оно в конечном итоге будет собирать мусор и собирать эти объекты, когда ему нужна память.

Б) если ваше приложение готово на этом этапе ... ну ... просто позвольте виртуальной машине умереть, и она вернет память обратно в ОС.

В любом случае настоящей «траты памяти» нет.

Задача сборщиков мусора состоит в том, чтобы со временем амортизировать затраты на сборку мусора. Это можно сделать, только отложив это до некоторого момента в будущем, вместо того, чтобы пытаться free() все сразу, как это сделали бы языки с ручным управлением.

Также обратите внимание, что ваша диаграмма показывает только опускающуюся использованную кучу (синий цвет). С точки зрения ОС выделенная куча (оранжевая) в любом случае остается неизменной, так что наклон вниз на синей диаграмме ничего вам не даст.

person the8472    schedule 31.03.2016
comment
Я перефразировал название, чтобы лучше отразить то, чего я пытаюсь достичь. A) в первую очередь необязательно выделять столько памяти, поэтому просто оставлять ее как есть неприемлемо. Б) это как раз на этапе загрузки. Я никогда специально не говорил, что эту проблему нужно решать с помощью _1 _... Я бы также предпочел решить ее каким-либо другим способом (возможно, мне не хватает библиотеки, которая делает это). Оранжевая линия не отражает фактический объем памяти, выделенной машиной. Я просто собираюсь удалить этот вопрос и создать свою собственную zip-систему с общим буфером, потому что здесь никто даже близко не подходит к теме. - person Obicere; 31.03.2016