Narazil jsem na stejný problém. Toto je známý problém s glibc>=2.10
Řešením je nastavení této proměnné env
export MALLOC_ARENA_MAX=4
Článek IBM o nastavení MALLOC_ARENA_MAX https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=cs
Google pro MALLOC_ARENA_MAX nebo jej vyhledejte na SO, abyste našli spoustu referencí.
Možná budete chtít vyladit také další možnosti malloc pro optimalizaci pro nízkou fragmentaci alokované paměti:
# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536
Je také možné, že došlo k úniku nativní paměti. Častým problémem jsou úniky nativní paměti způsobené neuzavřením ZipInputStream
/GZIPInputStream
.
Typický způsob, jak ZipInputStream
se otevře voláním na Class.getResource
/ClassLoader.getResource
a volání openConnection().getInputStream()
na java.net.URL
instance nebo voláním Class.getResourceAsStream
/ClassLoader.getResourceAsStream
. Je třeba zajistit, aby tyto proudy byly vždy uzavřeny.
Některé běžně používané knihovny s otevřeným zdrojovým kódem mají chyby, které unikají neuzavřeným java.util.zip.Inflater
nebo java.util.zip.Deflater
instance. Například knihovna Nimbus Jose JWT opravila související únik paměti ve verzi 6.5.1. Java JWT (jjwt) měla podobnou chybu, která byla opravena ve verzi 0.10.7. Vzorem chyby v těchto 2 případech byla skutečnost, že volání na DeflaterOutputStream.close()
a InflaterInputStream.close()
nevolejte Deflater.end()
/Inflater.end()
když Deflater
/Inflater
instance je poskytnuta. V těchto případech nestačí zkontrolovat kód pro zavřené streamy. Každých Deflater
/Inflater
instance vytvořené v kódu musí mít zpracování .end()
zavolá.
Jedním ze způsobů, jak zkontrolovat netěsnosti Zip*Stream, je získat výpis haldy a vyhledat instance jakékoli třídy s „zip“, „Inflater“ nebo „Deflater“ v názvu. To je možné v mnoha nástrojích pro analýzu výpisu haldy, jako je Yourkit Java Profiler, JProfiler nebo Eclipse MAT. Vyplatí se také kontrolovat objekty ve stavu finalizace, protože v některých případech se paměť uvolní až po finalizaci. Užitečná je kontrola tříd, které by mohly používat nativní knihovny. To platí i pro knihovny TLS/ssl.
Existuje nástroj OSS s názvem leakchecker od Elastic, což je Java Agent, který lze použít k nalezení zdrojů java.util.zip.Inflater
instance, které nebyly uzavřeny (.end()
nevoláno).
Pro úniky nativní paměti obecně (nejen pro úniky z knihovny zip) můžete použít jemalloc k ladění úniků nativní paměti tím, že povolíte profilování vzorkování malloc zadáním nastavení v MALLOC_CONF
proměnná prostředí. Podrobné pokyny jsou k dispozici v tomto příspěvku na blogu:http://www.evanjones.ca/java-native-leak-bug.html . Tento blogový příspěvek také obsahuje informace o použití jemalloc k ladění nativního úniku paměti v aplikacích Java. K dispozici je také blogový příspěvek od Elastic, který obsahuje jemalloc a zmiňuje leakchecker, nástroj, který Elastic vytvořil jako opensource pro sledování problémů způsobených neuzavřenými zdroji zip inflater.
Na blogu je také příspěvek o úniku nativní paměti související s ByteBuffers. Java 8u102 má speciální systémovou vlastnost jdk.nio.maxCachedBufferSize
omezit problém s mezipamětí popsaný v tomto blogovém příspěvku.
-Djdk.nio.maxCachedBufferSize=262144
Je také dobré vždy zkontrolovat popisovače otevřených souborů, abyste zjistili, zda únik paměti není způsoben velkým množstvím souborů mmap:ed. V systému Linux lsof
lze použít k zobrazení seznamu otevřených souborů a otevřených soketů:
lsof -Pan -p PID
Zpráva o paměťové mapě procesu by také mohla pomoci při vyšetřování úniků nativní paměti
pmap -x PID
Pro procesy Java běžící v Dockeru by mělo být možné spustit příkaz lsof nebo pmap na "hostiteli". Pomocí tohoto příkazu
můžete zjistit PID kontejnerizovaného procesudocker inspect --format '{{.State.Pid}}' container_id
Je také užitečné získat výpis vláken (nebo použít jconsole/JMX) ke kontrole počtu vláken, protože každé vlákno spotřebovává 1 MB nativní paměti pro svůj zásobník. Velký počet vláken by zabral hodně paměti.
V JVM je také nativní sledování paměti (NMT). To by mohlo být užitečné ke kontrole, zda je to samotné JVM, kdo využívá nativní paměť.
Nástroj jattach lze použít také v kontejnerovém (dockerovém) prostředí ke spouštění výpisů vláken nebo výpisů z hostitele. Je také schopen spouštět příkazy jcmd, které jsou potřebné pro ovládání NMT.