Dies ist eine langjährige Beschwerde bei Java, die jedoch weitgehend bedeutungslos ist und normalerweise auf der Suche nach falschen Informationen beruht. Die übliche Formulierung lautet etwa "Hallo Welt auf Java benötigt 10 Megabyte! Warum braucht es das?" Nun, hier ist eine Möglichkeit, Hello World auf einer 64-Bit-JVM dazu zu bringen, 4 Gigabyte zu übernehmen ... zumindest durch eine Messform.
java -Xms1024m -Xmx4096m com.example.Hello
Verschiedene Möglichkeiten zur Messung des Speichers
Unter Linux die Spitze gibt Ihnen Befehl mehrere verschiedene Nummern für den Speicher. Hier ist, was es über das Hello World-Beispiel sagt:
PID BENUTZER PR NI VIRT RES SHR S% CPU% MEM ZEIT + BEFEHL
2120 kgregory 20 0 4373 m 15 m 7152 S 0 0,2 0: 00,10 java
- VIRT ist der virtuelle Speicherplatz: die Summe aller Elemente in der virtuellen Speicherzuordnung (siehe unten). Es ist weitgehend bedeutungslos, außer wenn es nicht 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 des residenten Speichers, die mit anderen Prozessen gemeinsam genutzt wird. Bei einem Java-Prozess ist dies normalerweise auf gemeinsam genutzte Bibliotheken und JAR-Dateien mit Speicherzuordnung beschränkt. In diesem Beispiel wurde nur ein Java-Prozess ausgeführt, daher vermute ich, dass 7k ein Ergebnis von Bibliotheken ist, die vom Betriebssystem verwendet werden.
- SWAP ist standardmäßig nicht aktiviert und wird hier nicht angezeigt. Es gibt die Menge des virtuellen Speichers an, der sich derzeit auf der Festplatte befindet, unabhängig davon , ob er sich tatsächlich im Swap-Bereich befindet oder nicht . Das Betriebssystem ist sehr gut darin, aktive Seiten im RAM zu halten, und die einzigen Möglichkeiten zum Austauschen sind (1) mehr Speicher zu kaufen oder (2) die Anzahl der Prozesse zu reduzieren. Daher ist es am besten, diese Anzahl zu ignorieren.
Die Situation für den Windows Task-Manager ist etwas komplizierter. Unter Windows XP gibt es Spalten "Speichernutzung" und "Größe des virtuellen Speichers", aber in der offiziellen Dokumentation wird nicht angegeben, was sie bedeuten. Windows Vista und Windows 7 fügen weitere Spalten hinzu, die tatsächlich dokumentiert sind . Von diesen ist die Messung "Arbeitssatz" am nützlichsten; es entspricht ungefähr der Summe von RES und SHR unter Linux.
Grundlegendes zur virtuellen Speicherzuordnung
Der von einem Prozess belegte virtuelle Speicher ist die Summe aller Elemente in der Prozessspeicherzuordnung. Dies umfasst Daten (z. B. den Java-Heap), aber auch alle vom Programm verwendeten gemeinsam genutzten Bibliotheken und Speicherzuordnungsdateien. Unter Linux können Sie den Befehl pmap verwenden , um alle Dinge anzuzeigen , die dem Prozessbereich zugeordnet sind (von nun an werde ich nur noch auf Linux verweisen, da es das ist, was ich verwende; ich bin sicher, dass es gleichwertige Tools gibt Windows). Hier ist ein Auszug aus der Speicherkarte des Programms "Hello World". Die gesamte Speicherkarte ist über 100 Zeilen lang und es ist nicht ungewöhnlich, eine Liste mit tausend Zeilen zu haben.
0000000040000000 36K rx-- /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 rx-- /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 rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /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. Dieses 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 bei der Eingabe ausgeführt wird
java
). Das ist sehr klein; Alles, was es tut, ist das Laden in die gemeinsam genutzten Bibliotheken, in denen der echte JVM-Code gespeichert ist.
- Eine Reihe von Anon-Blöcken, die den Java-Heap und interne Daten enthalten. Dies ist eine Sun-JVM, daher ist der Heap in mehrere Generationen unterteilt, von denen jede einen eigenen Speicherblock hat. Beachten Sie, dass die JVM basierend auf dem
-Xmx
Wert virtuellen Speicherplatz zuweist. Dies ermöglicht es ihm, einen zusammenhängenden Haufen zu haben. Der -Xms
Wert wird intern verwendet, um anzugeben, wie viel des Heapspeichers beim Starten des Programms "verwendet" wird, und um die Speicherbereinigung auszulösen, wenn diese Grenze erreicht wird.
- Eine JAR-Datei mit Speicherzuordnung, in diesem Fall die Datei, die die "JDK-Klassen" enthält. Wenn Sie eine JAR im Speicher abbilden, können Sie sehr effizient auf die darin enthaltenen Dateien zugreifen (anstatt sie jedes Mal von Anfang an zu lesen). Die Sun-JVM ordnet alle JARs im Klassenpfad zu. Wenn Ihr Anwendungscode auf eine JAR zugreifen muss, können Sie diese auch im Speicher abbilden.
- Pro-Thread-Daten für zwei Threads. Der 1M-Block ist der Thread-Stapel. Ich hatte keine gute Erklärung für den 4k-Block, aber @ericsoe identifizierte ihn als "Schutzblock": Er hat keine Lese- / Schreibberechtigungen, verursacht also einen Segmentfehler, wenn darauf zugegriffen wird, und die JVM fängt diesen ab und übersetzt es zu einem
StackOverFlowError
. Bei einer echten App werden Dutzende, wenn nicht Hunderte dieser Einträge über die Speicherkarte wiederholt.
- Eine der gemeinsam genutzten Bibliotheken, die den tatsächlichen JVM-Code enthält. Es gibt mehrere davon.
- Die gemeinsam genutzte Bibliothek für die C-Standardbibliothek. Dies ist nur eines von vielen Dingen, die die JVM lädt und die nicht ausschließlich Teil von Java sind.
Die gemeinsam genutzten Bibliotheken sind besonders interessant: Jede gemeinsam genutzte Bibliothek hat mindestens zwei Segmente: ein schreibgeschütztes Segment, das den Bibliothekscode enthält, und ein Lese- / Schreibsegment, das globale Prozessdaten für die Bibliothek enthält (ich weiß nicht, was das ist Segment ohne Berechtigungen ist; ich habe es nur unter x64 Linux gesehen). Der schreibgeschützte Teil der Bibliothek kann von allen Prozessen gemeinsam genutzt werden, die die Bibliothek verwenden. libc
Verfügt beispielsweise über 1,5 MB virtuellen Speicherplatz, der gemeinsam genutzt werden kann.
Wann ist die Größe des virtuellen Speichers wichtig?
Die virtuelle Speicherkarte enthält viele Dinge. Ein Teil davon ist schreibgeschützt, ein Teil wird gemeinsam genutzt und ein Teil wird zugewiesen, aber nie berührt (z. B. fast alle 4 GB Heap in diesem Beispiel). Das Betriebssystem ist jedoch intelligent genug, um nur das zu laden, was es benötigt, sodass die Größe des virtuellen Speichers weitgehend irrelevant ist.
Die Größe des virtuellen Speichers ist wichtig, wenn Sie auf einem 32-Bit-Betriebssystem arbeiten, auf 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 Größe Ihres Heapspeichers, um eine große Datei im Speicher abzubilden oder viele Threads zu erstellen.
Angesichts der Tatsache, dass 64-Bit-Computer allgegenwärtig sind, wird es meiner Meinung nach nicht lange dauern, bis die Größe des virtuellen Speichers eine völlig irrelevante Statistik ist.
Wann ist die Größe des Resident-Sets wichtig?
Resident Set-Größe ist der Teil des virtuellen Speicherplatzes, der sich tatsächlich im RAM befindet. Wenn Ihr RSS zu einem bedeutenden Teil Ihres gesamten physischen Speichers wird, ist es möglicherweise an der Zeit, sich Sorgen zu machen. Wenn Ihr RSS wächst und Ihren gesamten physischen Speicher belegt und Ihr System mit dem Austausch beginnt, ist es längst vorbei, sich Sorgen zu machen.
RSS ist aber auch irreführend, insbesondere auf einer leicht geladenen Maschine. Das Betriebssystem ist nicht sehr aufwendig, um die von einem Prozess verwendeten Seiten zurückzugewinnen. Dies hat wenig Vorteile und das Potenzial für einen teuren Seitenfehler, wenn der Prozess die Seite in Zukunft berührt. Infolgedessen kann die RSS-Statistik viele Seiten enthalten, die nicht aktiv verwendet werden.
Endeffekt
Machen Sie sich keine allzu großen Sorgen darüber, was die verschiedenen Speicherstatistiken Ihnen sagen, es sei denn, Sie tauschen. Mit der Einschränkung, dass ein ständig wachsender RSS-Code auf eine Art Speicherverlust hinweisen kann.
Bei einem Java-Programm ist es viel wichtiger, darauf zu achten, was auf dem Heap passiert. Der Gesamtplatzbedarf ist wichtig, und Sie können einige Schritte unternehmen, um dies zu reduzieren. Wichtiger ist die Zeit, die Sie für die Speicherbereinigung aufwenden, und welche Teile des Heaps gesammelt werden.
Der Zugriff auf die Festplatte (dh eine Datenbank) ist teuer und der Speicher ist billig. Wenn Sie einen gegen den anderen tauschen können, tun Sie dies.