Dies ist eine seit langem bestehende Beschwerde bei Java, aber sie ist weitgehend bedeutungslos und basiert normalerweise auf dem Betrachten der falschen Informationen. Die übliche Formulierung ist so etwas wie "Hello World auf Java braucht 10 Megabyte! Warum braucht es das?" Nun, hier ist eine Möglichkeit, Hello World auf einer 64-Bit-JVM zu behaupten, 4 Gigabyte zu übernehmen ... zumindest nach einer Form der Messung.
java -Xms1024m -Xmx4096m com.example.Hello
Verschiedene Möglichkeiten, das Gedächtnis zu messen
Unter Linux gibt Ihnen der oberste Befehl mehrere verschiedene Zahlen für den Speicher. Hier ist, was es über das Hello World-Beispiel sagt:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2120 kgregory 20 0 4373m 15m 7152 S 0 0.2 0:00.10 java
- VIRT ist der virtuelle Speicherplatz:die Summe von allem in der virtuellen Speicherabbildung (siehe unten). Es ist weitgehend bedeutungslos, außer wenn es nicht so ist (siehe unten).
- RES ist die Größe des residenten Satzes:die Anzahl der Seiten, die derzeit im RAM resident sind. In fast allen Fällen ist dies die einzige Zahl, die Sie verwenden sollten, wenn Sie „zu groß“ sagen. Aber es ist immer noch keine sehr gute Zahl, besonders wenn es um Java geht.
- SHR ist die Menge an residentem Speicher, die mit anderen Prozessen geteilt wird. Bei einem Java-Prozess ist dies normalerweise auf gemeinsam genutzte Bibliotheken und speicherabgebildete JAR-Dateien beschränkt. In diesem Beispiel wurde nur ein Java-Prozess ausgeführt, daher vermute ich, dass die 7k das Ergebnis von Bibliotheken ist, die vom Betriebssystem verwendet werden.
- SWAP ist standardmäßig nicht aktiviert und wird hier nicht angezeigt. Er gibt die Menge an virtuellem Speicher an, der sich derzeit auf der Festplatte befindet, unabhängig davon, ob er sich tatsächlich im Auslagerungsbereich befindet . Das Betriebssystem ist sehr gut darin, aktive Seiten im RAM zu halten, und die einzigen Heilmittel für das Austauschen sind (1) mehr Speicher zu kaufen oder (2) die Anzahl der Prozesse zu reduzieren, also ist es am besten, diese Zahl zu ignorieren.
Die Situation für den Windows Task-Manager ist etwas komplizierter. Unter Windows XP gibt es die Spalten „Memory Usage“ und „Virtual Memory Size“, aber die offizielle Dokumentation schweigt sich darüber aus, was sie bedeuten. Windows Vista und Windows 7 fügen weitere Spalten hinzu, und sie sind tatsächlich dokumentiert. Von diesen ist die "Working Set"-Messung am nützlichsten; es entspricht in etwa der Summe von RES und SHR unter Linux.
Die virtuelle Speicherzuordnung verstehen
Der von einem Prozess verbrauchte virtuelle Speicher ist die Summe aller Elemente, die sich in der Prozessspeicherzuordnung befinden. Dazu gehören Daten (z. B. der Java-Heap), aber auch alle gemeinsam genutzten Bibliotheken und Memory-Mapped-Dateien, die vom Programm verwendet werden. Unter Linux können Sie den Befehl pmap verwenden, um alle Dinge anzuzeigen, die im Prozessraum abgebildet sind (von hier an werde ich mich nur noch auf Linux beziehen, weil ich es verwende; ich bin mir sicher, dass es dafür gleichwertige Tools gibt Fenster). Hier ein Auszug aus der Memory Map des „Hello World“-Programms; Die gesamte Memory Map ist über 100 Zeilen lang, und es ist nicht ungewöhnlich, eine Liste mit tausend Zeilen zu haben.
0000000040000000 36K r-x-- /usr/local/java/jdk-1.6-x64/bin/java 0000000040108000 8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java 0000000040eba000 676K rwx-- [ anon ] 00000006fae00000 21248K rwx-- [ anon ] 00000006fc2c0000 62720K rwx-- [ anon ] 0000000700000000 699072K rwx-- [ anon ] 000000072aab0000 2097152K rwx-- [ anon ] 00000007aaab0000 349504K rwx-- [ anon ] 00000007c0000000 1048576K rwx-- [ anon ] ... 00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar ... 00007fa1ed1d3000 1024K rwx-- [ anon ] 00007fa1ed2d3000 4K ----- [ anon ] 00007fa1ed2d4000 1024K rwx-- [ anon ] 00007fa1ed3d4000 4K ----- [ anon ] ... 00007fa1f20d3000 164K r-x-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 00007fa1f21fb000 28K rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so ... 00007fa1f34aa000 1576K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so 00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so 00007fa1f3833000 16K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so 00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so ...
Eine kurze Erklärung des Formats:Jede Zeile beginnt mit der virtuellen Speicheradresse des Segments. Darauf folgen die Segmentgröße, Berechtigungen und die Quelle des Segments. Das letzte Element ist entweder eine Datei oder "anon", was einen über mmap zugewiesenen Speicherblock angibt.
Von oben beginnend haben wir
- Der JVM-Loader (dh das Programm, das ausgeführt wird, wenn Sie
java
eingeben ). Das ist sehr klein; es lädt lediglich die gemeinsam genutzten Bibliotheken, in denen der echte JVM-Code gespeichert ist. - Ein Haufen anonymer Blöcke, die den Java-Heap und interne Daten enthalten. Dies ist eine Sun-JVM, daher ist der Heap in mehrere Generationen unterteilt, von denen jede ein eigener Speicherblock ist. Beachten Sie, dass die JVM virtuellen Speicherplatz basierend auf
-Xmx
zuweist Wert; Dies ermöglicht es, einen zusammenhängenden Heap zu haben. Die-Xms
Der Wert wird intern verwendet, um anzugeben, wie viel Heap beim Programmstart "in Gebrauch" ist, und um die Garbage-Collection auszulösen, wenn diese Grenze erreicht wird. - Eine speicherabgebildete JAR-Datei, in diesem Fall die Datei, die die "JDK-Klassen" enthält. Wenn Sie ein JAR speicherzuordnen, können Sie sehr effizient auf die darin enthaltenen Dateien zugreifen (anstatt es jedes Mal von Anfang an zu lesen). Die Sun-JVM wird alle JARs im Klassenpfad speicherzuordnen; Wenn Ihr Anwendungscode auf ein JAR zugreifen muss, können Sie es auch speicherzuordnen.
- Daten pro Thread für zwei Threads. Der 1M-Block ist der Thread-Stack. Ich hatte keine gute Erklärung für den 4k-Block, aber @ericsoe hat ihn als "Wachblock" identifiziert:Er hat keine Lese-/Schreibberechtigungen, verursacht also einen Segmentfehler, wenn darauf zugegriffen wird, und die JVM fängt das ab und übersetzt auf
StackOverFlowError
. Bei einer echten App sehen Sie Dutzende, wenn nicht Hunderte dieser Einträge, die sich durch die Speicherzuordnung wiederholen. - Eine der gemeinsam genutzten Bibliotheken, die den eigentlichen JVM-Code enthält. Davon gibt es mehrere.
- Die gemeinsam genutzte Bibliothek für die C-Standardbibliothek. Dies ist nur eines von vielen Dingen, die die JVM lädt, die nicht unbedingt Teil von Java sind.
Die gemeinsam genutzten Bibliotheken sind besonders interessant:Jede gemeinsam genutzte Bibliothek hat mindestens zwei Segmente:ein Nur-Lese-Segment, das den Bibliothekscode enthält, und ein Lese-Schreib-Segment, das globale Pro-Prozess-Daten für die Bibliothek enthält (ich weiß nicht, was die Segment ohne Berechtigungen ist; ich habe es nur auf x64 Linux gesehen). Der schreibgeschützte Teil der Bibliothek kann von allen Prozessen gemeinsam genutzt werden, die die Bibliothek verwenden; zum Beispiel libc
verfügt über 1,5 MB virtuellen Speicherplatz, der gemeinsam genutzt werden kann.
Wann ist die Größe des virtuellen Speichers wichtig?
Die virtuelle Memory Map enthält eine Menge Zeug. Ein Teil davon ist schreibgeschützt, ein Teil davon wird gemeinsam genutzt und ein Teil davon wird zugewiesen, aber nie berührt (z. B. fast der gesamte 4-GB-Heap in diesem Beispiel). Aber das Betriebssystem ist schlau genug, nur das zu laden, was es braucht, daher ist die Größe des virtuellen Speichers weitgehend irrelevant.
Die Größe des virtuellen Speichers ist wichtig, wenn Sie ein 32-Bit-Betriebssystem verwenden, bei dem Sie nur 2 GB (oder in einigen Fällen 3 GB) Prozessadressraum zuweisen können. In diesem Fall haben Sie es mit einer knappen Ressource zu tun und müssen möglicherweise Kompromisse eingehen, wie z. B. die Reduzierung Ihrer Heap-Größe, um eine große Datei im Speicher abzubilden oder viele Threads zu erstellen.
Aber angesichts der Tatsache, dass 64-Bit-Rechner allgegenwärtig sind, glaube ich nicht, dass es lange dauern wird, bis die Größe des virtuellen Speichers eine völlig irrelevante Statistik ist.
Wann ist die Resident Set-Größe wichtig?
Die Resident Set-Größe ist der Teil des virtuellen Speicherplatzes, der sich tatsächlich im RAM befindet. Wenn Ihr RSS zu einem erheblichen Teil Ihres gesamten physischen Speichers wird, ist es möglicherweise an der Zeit, sich Sorgen zu machen. Wenn Ihr RSS so anwächst, dass es Ihren gesamten physischen Speicher belegt, und Ihr System mit Auslagerungen beginnt, ist es längst an der Zeit, sich Sorgen zu machen.
Aber RSS ist auch irreführend, besonders auf einem leicht belasteten Rechner. Das Betriebssystem wendet nicht viel Aufwand auf, um die von einem Prozess verwendeten Seiten zurückzufordern. Dadurch ergibt sich kaum ein Vorteil und es besteht die Möglichkeit eines teuren Seitenfehlers, wenn der Prozess die Seite in Zukunft berührt. Infolgedessen enthält die RSS-Statistik möglicherweise viele Seiten, die nicht aktiv verwendet werden.
Unterm Strich
Machen Sie sich keine allzu großen Sorgen darüber, was Ihnen die verschiedenen Speicherstatistiken sagen, es sei denn, Sie tauschen aus. Mit der Einschränkung, dass ein ständig wachsender RSS auf eine Art Speicherleck hindeuten kann.
Bei einem Java-Programm ist es viel wichtiger, darauf zu achten, was im Heap passiert. Die Gesamtmenge an verbrauchtem Speicherplatz ist wichtig, und es gibt einige Schritte, die Sie unternehmen können, um dies zu reduzieren. Wichtiger ist die Zeit, die Sie mit der Garbage Collection verbringen und welche Teile des Heaps gesammelt werden.
Der Zugriff auf die Platte (dh eine Datenbank) ist teuer und Speicher ist billig. Wenn Sie das eine gegen das andere tauschen können, tun Sie dies.
Die für den Java-Prozess zugewiesene Speichermenge entspricht ziemlich genau meinen Erwartungen. Ich hatte ähnliche Probleme beim Ausführen von Java auf eingebetteten / speicherbegrenzten Systemen. Ausführen von any Anwendungen mit willkürlichen VM-Limits oder auf Systemen, die nicht über ausreichende Swap-Mengen verfügen, neigen dazu, zu brechen. Es scheint die Natur vieler moderner Apps zu sein, die nicht für die Verwendung auf ressourcenbeschränkten Systemen konzipiert sind.
Sie haben einige weitere Optionen, die Sie ausprobieren können, um den Speicherbedarf Ihrer JVM zu begrenzen. Dies kann den Platzbedarf des virtuellen Speichers verringern:
-XX:ReservedCodeCacheSize=32m Reservierte Code-Cache-Größe (in Bytes) – maximale Code-Cache-Größe. [Solaris 64-Bit, AMD64 und -Server x86:48 Min.; in1.5.0_06 und früher, Solaris 64-Bit und and64:1024m.]
-XX:MaxPermSize=64m Größe der permanenten Generation. [5.0 und neuer:64-Bit-VMs werden um 30 % größer skaliert; 1.4amd64:96m; 1.3.1 -client:32m.]
Außerdem sollten Sie -Xmx (max. Heap-Größe) auf einen Wert setzen, der so nah wie möglich an der tatsächlichen Spitzenspeichernutzung liegt Ihrer Bewerbung. Ich glaube, das Standardverhalten der JVM ist immer noch double die Heap-Größe jedes Mal, wenn es bis zum Maximum erweitert wird. Wenn Sie mit 32 Mio. Heap beginnen und Ihre App einen Spitzenwert von 65 Mio. erreicht hat, wächst der Heap am Ende um 32 Mio. -> 64 Mio. -> 128 Mio.
Sie können auch Folgendes versuchen, um die VM weniger aggressiv beim Heap-Wachstum zu machen:
-XX:MinHeapFreeRatio=40 Mindestprozentsatz an freiem Heap nach GC, um eine Erweiterung zu vermeiden.
Soweit ich mich von Experimenten damit vor einigen Jahren erinnere, hatte die Anzahl der geladenen nativen Bibliotheken einen großen Einfluss auf den minimalen Platzbedarf. Das Laden von java.net.Socket fügte mehr als 15 Millionen hinzu, wenn ich mich richtig erinnere (und ich tue es wahrscheinlich nicht).
Es gibt ein bekanntes Problem mit Java und glibc>=2.10 (einschließlich Ubuntu>=10.04, RHEL>=6).
Das Heilmittel besteht darin, diese Umgebung festzulegen. Variable:
export MALLOC_ARENA_MAX=4
Wenn Sie Tomcat ausführen, können Sie dies zu TOMCAT_HOME/bin/setenv.sh
hinzufügen Datei.
Fügen Sie dies für Docker zu Dockerfile
hinzuENV MALLOC_ARENA_MAX=4
Es gibt einen IBM-Artikel über das Einstellen von MALLOC_ARENA_MAXhttps://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en
In diesem Blogpost steht
Es ist bekannt, dass der residente Speicher ähnlich wie ein Speicherleck oder eine Speicherfragmentierung kriecht.
Es gibt auch einen offenen JDK-Bug JDK-8193521 „glibc wastes memory with default configuration“
Suchen Sie bei Google oder SO nach MALLOC_ARENA_MAX, um weitere Referenzen zu erhalten.
Möglicherweise möchten Sie auch andere malloc-Optionen optimieren, um eine geringe Fragmentierung des zugewiesenen Speichers zu optimieren:
# 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