Dies ist eine seit langem bestehende Beschwerde über Java, aber sie ist weitgehend bedeutungslos und basiert in der Regel auf der Betrachtung 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 so zu gestalten, dass es über 4 Gigabyte benötigt ... zumindest nach einer Art von Messung.
java -Xms1024m -Xmx4096m com.example.Hello
Verschiedene Arten der Gedächtnismessung
Unter Linux wird die top gibt Ihnen verschiedene Zahlen für den Speicher an. 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 aller Daten in der virtuellen Speicherkarte (siehe unten). Er ist weitgehend bedeutungslos, außer wenn er es nicht ist (siehe unten).
- RES ist die Größe des residenten Speichers: die Anzahl der Seiten, die sich derzeit im RAM befinden. 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 des residenten Speichers, die mit anderen Prozessen geteilt wird. Bei einem Java-Prozess beschränkt sich dies in der Regel auf gemeinsam genutzte Bibliotheken und im Speicher abgebildete JAR-Dateien. In diesem Beispiel lief nur ein Java-Prozess, so dass ich vermute, dass die 7k auf die vom Betriebssystem verwendeten Bibliotheken zurückzuführen sind.
- SWAP ist standardmäßig nicht aktiviert und wird hier nicht angezeigt. Sie zeigt die Menge an virtuellem Speicher an, die sich derzeit auf der Festplatte befindet, ob es sich tatsächlich im Swap Space befindet oder nicht . Das Betriebssystem ist sehr gut darin, aktive Seiten im Arbeitsspeicher zu halten, und die einzigen Heilmittel für das Auslagern sind (1) mehr Arbeitsspeicher zu kaufen oder (2) die Anzahl der Prozesse zu reduzieren, daher ist es am besten, diese Zahl zu ignorieren.
Beim Windows Task Manager ist die Situation etwas komplizierter. Unter Windows XP gibt es die Spalten "Speichernutzung" und "Größe des virtuellen Speichers", aber die offizielle Dokumentation sagt nichts darüber aus, was sie bedeuten. Windows Vista und Windows 7 fügen mehr Spalten hinzu, und sie sind tatsächlich dokumentiert . Davon ist die Messung des "Working Set" am nützlichsten; sie entspricht ungefähr der Summe von RES und SHR unter Linux.
Die Abbildung des virtuellen Speichers verstehen
Der virtuelle Speicher, der von einem Prozess verbraucht wird, ist die Summe aller Daten, die sich in der Speicherabbildung des Prozesses befinden. Dazu gehören Daten (z. B. der Java-Heap), aber auch alle gemeinsam genutzten Bibliotheken und Dateien, die vom Programm im Speicher abgebildet werden. Unter Linux können Sie die pmap um alle Dinge zu sehen, die dem Prozessraum zugeordnet sind (von nun an werde ich mich nur noch auf Linux beziehen, weil ich es benutze; ich bin mir sicher, dass es entsprechende Tools für Windows gibt). Hier ist ein Auszug aus der Speicherabbildung des "Hello World"-Programms; die gesamte Speicherabbildung ist über 100 Zeilen lang, und es ist nicht ungewöhnlich, eine tausendzeilige Liste 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 Adresse des virtuellen Speichers des Segments. Darauf folgen die Segmentgröße, die Berechtigungen und die Quelle des Segments. Das letzte Element ist entweder eine Datei oder "anon", was auf einen Speicherblock hinweist, der über mmap .
Von oben beginnend, haben wir
- Der JVM-Lader (d.h. das Programm, das ausgeführt wird, wenn Sie
java
). Diese ist sehr klein; sie lädt lediglich die gemeinsam genutzten Bibliotheken, in denen der eigentliche JVM-Code gespeichert ist.
- Ein Haufen anonymer Blöcke, die den Java-Heap und interne Daten enthalten. Da es sich um eine JVM von Sun handelt, ist der Heap in mehrere Generationen unterteilt, von denen jede einen eigenen Speicherblock darstellt. Beachten Sie, dass die JVM den virtuellen Speicherplatz auf der Grundlage des
-Xmx
Wert; dies ermöglicht es, einen zusammenhängenden Heap zu haben. Die Website -Xms
Wert wird intern verwendet, um zu sagen, wie viel des Heaps "in Gebrauch" ist, wenn das Programm startet, und um die Garbage Collection auszulösen, wenn sich diese Grenze nähert.
- Eine memory-mapped JAR-Datei, in diesem Fall die Datei, die die "JDK-Klassen" enthält. Wenn Sie ein Memory-Mapping für eine JAR-Datei durchführen, können Sie sehr effizient auf die darin enthaltenen Dateien zugreifen (im Gegensatz zum Lesen der Datei von Anfang an). Die Sun JVM ordnet alle JARs auf dem Klassenpfad dem Speicher zu; wenn Ihr Anwendungscode auf ein JAR zugreifen muss, können Sie es ebenfalls dem Speicher zuordnen.
- Pro-Thread-Daten für zwei Threads. Der 1M-Block ist der Thread-Stack. Ich hatte keine gute Erklärung für den 4k-Block, aber @ericsoe identifizierte ihn als "Guard-Block": Er hat keine Lese-/Schreibrechte und verursacht daher einen Segmentfehler, wenn auf ihn zugegriffen wird, und die JVM fängt das ab und übersetzt es in einen
StackOverFlowError
. Bei einer echten Anwendung werden Sie Dutzende, wenn nicht Hunderte dieser Einträge sehen, die sich in der Memory Map wiederholen.
- Eine der gemeinsam genutzten Bibliotheken, die den eigentlichen JVM-Code enthält. Hiervon gibt es mehrere.
- Die gemeinsam genutzte Bibliothek für die C-Standardbibliothek. Dies ist nur eines der vielen Dinge, die die JVM lädt und die nicht unbedingt zu Java gehören.
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 prozessspezifische Daten für die Bibliothek enthält (ich weiß nicht, was das Segment ohne Berechtigungen ist; ich habe es nur unter x64 Linux gesehen). Der schreibgeschützte Teil der Bibliothek kann von allen Prozessen, die die Bibliothek verwenden, gemeinsam genutzt werden; zum Beispiel, libc
hat 1,5M virtuellen Speicherplatz, der gemeinsam genutzt werden kann.
Wann ist die Größe des virtuellen Speichers wichtig?
Die virtuelle Speicherkarte enthält eine Menge Dinge. Einiges davon ist schreibgeschützt, einiges wird gemeinsam genutzt, und einiges wird zugewiesen, aber nie berührt (z.B. fast die gesamten 4 GB des Heaps in diesem Beispiel). Aber das Betriebssystem ist klug genug, nur das zu laden, was es braucht, so dass die Größe des virtuellen Speichers weitgehend irrelevant ist.
Die Größe des virtuellen Speichers ist wichtig, wenn Sie mit einem 32-Bit-Betriebssystem arbeiten, 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, z. B. die Heap-Größe reduzieren, um eine große Datei im Speicher abzubilden oder viele Threads zu erstellen.
Da aber 64-Bit-Maschinen allgegenwärtig sind, wird es wohl nicht mehr lange dauern, bis die Größe des virtuellen Speichers eine völlig irrelevante Statistik ist.
Wann ist die Größe des Residenten-Sets wichtig?
Die Resident Set-Größe ist der Teil des virtuellen Speicherplatzes, der sich tatsächlich im RAM befindet. Wenn Ihr RSS einen beträchtlichen Teil des gesamten physischen Speichers einnimmt, ist es vielleicht an der Zeit, sich Sorgen zu machen. Wenn Ihr RSS den gesamten physischen Speicher beansprucht und Ihr System anfängt zu swappen, 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 betreibt keinen großen Aufwand, um die von einem Prozess belegten Seiten zurückzufordern. Der Nutzen ist gering, und es besteht die Gefahr eines teuren Seitenfehlers, wenn der Prozess die Seite in Zukunft berührt. Infolgedessen kann die RSS-Statistik viele Seiten enthalten, die nicht aktiv genutzt werden.
Unterm Strich
Solange Sie nicht auslagern, sollten Sie sich nicht zu viele Gedanken darüber machen, was die verschiedenen Speicherstatistiken Ihnen sagen. Mit dem Vorbehalt, dass eine ständig wachsende RSS auf eine Art von Speicherleck hinweisen kann.
Bei einem Java-Programm ist es viel wichtiger, darauf zu achten, was im Heap passiert. Die Gesamtmenge des verbrauchten Platzes 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 Festplatte (z. B. eine Datenbank) ist teuer, der Arbeitsspeicher ist billig. Wenn Sie das eine gegen das andere tauschen können, sollten Sie das tun.