Управление на паметта, разпределена при зареждане на ZipFile

Опитвам се да заредя 69 930 файла в основен текстов редактор. Това върви гладко и след като всички са заредени, паметта остава на много готини 130MB. По време на пиковото време на зареждане обаче това може да достигне максимум 900MB - 1200MB.

Цялата памет препраща към Inflater#buf поле. Това се използва само за зареждане на файла в обектния модел, след което никога повече не се използва и байтовете могат да бъдат изчистени.

Очевидно цялата допълнителна памет се изчиства от събирача на отпадъци скоро след зареждането - така че няма изтичане на памет. Въпреки това изглежда просто ненужно да се използва толкова много допълнителна памет.

Какво съм пробвал:

  1. Проблемът с паметта се „разрешава“ чрез извършване на System.gc() извикване веднага след затваряне на ZipFile. Това води до ~75% време за наблюдение на нишките, високо натоварване на процесора и бавно време на зареждане.
  2. Намаляване на броя на пула от нишки. Това намали въздействието (до 300MB), но доведе до значително по-дълго време за зареждане.
  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 изрично. Не е изтичане на памет. 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
От повърхността на новия ви резултат, общото време не се различава много. Но в някои curcial системи за мисии в реално време (напр. система за борсова търговия), това може да бъде смъртоносно. Когато правите GC, особено малък GC, забавянето на услугата (през този кратък период) може да причини голяма финансова загуба, само поради няколкостотин милисекунди.   -  person user218867    schedule 29.03.2016
comment
Може би опитайте с някакъв вид буфер, за да заредите по-малки части от ZIP, за да работите с тях. Ако искате да заредите целия ZIP файл в паметта, поведението, което сте показали, е очаквано.   -  person Jire    schedule 29.03.2016
comment
@Jire има 576 отделни буркана. Най-големият, който видях при бързо сканиране, беше около 4 MB. Бих казал, че има много достатъчно малки парчета.   -  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, това е същото. Когато се случи голям GC, той ще премахне всички колекционерски обекти и след това ще преоразмери купчината съответно, позволявайки толкова много загуби (което има причина), колкото е конфигурирано от тези параметри. Какво повече от това очаквате? Не можете да освободите памет, която все още се използва.   -  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) ако вашето приложение продължава да работи, то в крайна сметка ще GC и ще събере тези обекти, когато се нуждае от паметта.

B) ако вашето приложение е готово в този момент... добре... просто оставете VM да умре и тя ще освободи паметта обратно към операционната система.

Така или иначе, няма истинска „загуба на памет“.

Целта на сметосъбирачите е да амортизират разходите за събиране с течение на времето. Може да направи това само като го отложи за някакъв момент в бъдещето, вместо да се опитва да free() всичко веднага, както биха направили ръчно управляваните езици.

Също така имайте предвид, че вашата диаграма показва само използваната купчина (синя), която намалява. От гледна точка на ОС разпределената купчина (оранжева) така или иначе остава същата, така че наклонът надолу върху синята диаграма не ви носи нищо.

person the8472    schedule 31.03.2016
comment
Преформулирах заглавието, за да отразява по-добре това, което се опитвам да постигна. A) не е необходимо да имате толкова много памет, разпределена на първо място, така че просто да го оставите както е, не е приемливо. B) това е само във фазата на зареждане. Никога не съм казвал конкретно, че това трябва да се реши с помощта на System.gc()... Бих предпочел също да го реша по друг начин (може би ми липсва библиотека, която прави това). Оранжевата линия не отразява действителната памет, разпределена от машината. Просто ще изтрия този въпрос и ще направя своя собствена zip система със споделен буфер, защото никой тук дори не е близо до темата. - person Obicere; 31.03.2016