web-dev-qa-db-ger.com

Nutzung des virtuellen Speichers von Java unter Linux wird zu viel Speicher verwendet

Ich habe ein Problem mit einer Java -Anwendung, die unter Linux ausgeführt wird.

Wenn ich die Anwendung mit der standardmäßigen maximalen Heap-Größe (64 MB) starte, wird bei Verwendung der Top-Anwendung angezeigt, dass der Anwendung 240 MB virtueller Speicher zugewiesen sind. Dies führt zu Problemen mit anderer Software auf dem Computer, die relativ ressourcenbeschränkt ist.

Der reservierte virtuelle Speicher wird meines Wissens sowieso nicht verwendet, da bei Erreichen des Heap-Limits ein OutOfMemoryError geworfen wird. Ich habe die gleiche Anwendung unter Windows ausgeführt und sehe, dass die Größe des virtuellen Speichers und die Größe des Heapspeichers ähnlich sind.

Gibt es eine Möglichkeit, den verwendeten virtuellen Speicher für einen Java Prozess unter Linux zu konfigurieren?

Edit 1: Das Problem ist nicht der Heap. Das Problem ist, dass, wenn ich zum Beispiel einen Heap von 128 MB einstelle, Linux immer noch 210 MB virtuellen Speicher zuweist, was jedoch nie benötigt wird. **

Edit 2: Mit ulimit -v ermöglicht die Begrenzung des virtuellen Speichers. Wenn die festgelegte Größe unter 204 MB liegt, wird die Anwendung nicht ausgeführt, obwohl sie nicht 204 MB, sondern nur 64 MB benötigt. Ich möchte verstehen, warum Java benötigt so viel virtuellen Speicher. Kann dies geändert werden?

Edit: Auf dem System, das eingebettet ist, laufen mehrere andere Anwendungen. Und das System hat ein virtuelles Speicherlimit (aus Kommentaren, wichtige Details).

241
Mario Ortegón

Dies ist eine langjährige Beschwerde bei Java, die jedoch weitgehend bedeutungslos ist und in der Regel auf der Suche nach falschen Informationen beruht. Die übliche Formulierung ist so etwas wie "Hallo Welt auf Java braucht 10 Megabyte! Warum braucht es das?" Nun, hier ist eine Möglichkeit, Hallo Welt auf eine 64-Bit-JVM zu bringen, die behauptet, sie zu übernehmen 4 Gigabyte ... zumindest nach einer Messform.

 Java -Xms1024m -Xmx4096m com.example.Hello 

Verschiedene Möglichkeiten zur Speichermessung

Unter Linux gibt Ihnen der Befehl top mehrere verschiedene Nummern für den Speicher an. Hier ist, was es über das Hello World Beispiel sagt:

 PID-BENUTZER PR NI VIRT RES SHR S% CPU% SPEICHERZEIT + BEFEHL 
 2120 kgSpeicher 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 größtenteils bedeutungslos, außer wenn es nicht so ist (siehe unten).
  • RES ist die residente Satzgröße: 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 Größe des residenten Speichers, der 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 habe ich vermuten, dass der 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 Größe des virtuellen Speichers 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 belassen, und die einzige Möglichkeit zum Auslagern besteht darin, (1) mehr Arbeitsspeicher zu kaufen oder (2) die Anzahl der Prozesse zu verringern. Es empfiehlt sich, 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". In der offiziellen Dokumentation wird jedoch nicht erläutert, was sie bedeuten. Windows Vista und Windows 7 fügen weitere Spalten hinzu und sie sind tatsächlich dokumentiert . 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. Dazu gehören Daten (z. B. der Java Heap)), aber auch alle vom Programm verwendeten gemeinsam genutzten Bibliotheken und Speicherzuordnungsdateien. Unter Linux können Sie den verwenden ) pmap Befehl, um alle im Prozessraum gemappten Dinge zu sehen (von jetzt an beziehe ich mich nur noch auf Linux, weil es das ist, was ich benutze; ich bin sicher, es gibt äquivalente Tools für Windows Hier ist ein Auszug aus der Speicherkarte des "Hello World" -Programms: 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[.____.‹00000040108000 8K rwx-- /usr/local/Java/jdk-1.6-x64/bin /Java[.____.owntown00000040eba000 676K rwx-- [anon] 
 00000006fae00000 21248K rwx-- [anon] 
 00000006fc2c0000 62720K rwx-- [anon] 
 0000000700000000 699072 - [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[.____.owntown00007fa1f3837000 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, die Berechtigungen und die Quelle des Segments. Dieses letzte Element ist entweder eine Datei oder "anon", die einen über mmap zugewiesenen Speicherblock angibt.

Ausgehend von der Spitze haben wir

  • Das JVM-Ladeprogramm (dh das Programm, das ausgeführt wird, wenn Sie Java eingeben). Das ist sehr klein; Es wird lediglich in die gemeinsam genutzten Bibliotheken geladen, 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, sodass der Heap in mehrere Generationen unterteilt ist, von denen jede einen eigenen Speicherblock darstellt. Beachten Sie, dass die JVM reserviert virtuellen Speicherplatz basierend auf dem -Xmx Wert; Dies ermöglicht es, einen zusammenhängenden Heap zu haben. Das -Xms value wird intern verwendet, um anzugeben, wie viel des Heaps beim Start des Programms "belegt" ist, und um die Garbage Collection auszulösen, wenn dieses Limit erreicht wird.
  • Eine speicherabgebildete JAR-Datei, in diesem Fall die Datei, die die "JDK-Klassen" enthält. Wenn Sie eine JAR-Datei im Speicher zuordnen, können Sie sehr effizient auf die darin enthaltenen Dateien zugreifen (anstatt sie jedes Mal von vorn zu lesen). Die Sun-JVM ordnet alle JARs auf dem Klassenpfad im Speicher zu. Wenn Ihr Anwendungscode auf eine JAR zugreifen muss, können Sie auch eine Speicherzuordnung vornehmen.
  • Pro-Thread-Daten für zwei Threads. Der 1M-Block ist ein Thread-Stapel. Ich weiß nicht, was in den 4K-Block geht. Für eine echte App werden Dutzende, wenn nicht Hunderte dieser Einträge in der Speicherkarte wiederholt.
  • Eine der gemeinsam genutzten Bibliotheken, die den eigentlichen 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 von der JVM geladen werden und die nicht ausschließlich in Java enthalten sind.

Besonders interessant sind die gemeinsam genutzten Bibliotheken: Jede gemeinsam genutzte Bibliothek hat mindestens zwei Segmente: ein schreibgeschütztes Segment, das den Bibliothekscode enthält, und ein schreibgeschütztes Segment, das globale prozessbezogene Daten 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. Beispielsweise verfügt libc über 1,5 MB virtuellen Speicher, der gemeinsam genutzt werden kann.

Wann ist die Größe des virtuellen Speichers wichtig?

Die virtuelle Speicherkarte enthält eine Menge Dinge. 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). 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 ein 32-Bit-Betriebssystem verwenden und 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 reduzieren, um eine große Datei zu speichern oder viele Threads zu erstellen.

Angesichts der Tatsache, dass 64-Bit-Computer allgegenwärtig sind, wird es jedoch nicht 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 residente Satzgröße ist der Teil des virtuellen Speicherbereichs, 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 wächst und Ihren gesamten physischen Speicher beansprucht und Ihr System zu tauschen beginnt, ist es längst an der Zeit, sich Sorgen zu machen.

RSS ist jedoch auch irreführend, insbesondere auf einem schwach ausgelasteten Computer. Das Betriebssystem ist nicht sehr bemüht, die von einem Prozess verwendeten Seiten zurückzugewinnen. Dies hat nur einen geringen Nutzen, und es besteht die Gefahr eines teuren Seitenfehlers, wenn der Prozess in Zukunft die Seite berührt. Infolgedessen enthält die RSS-Statistik möglicherweise viele Seiten, die nicht aktiv verwendet werden.

Endeffekt

Machen Sie sich keine allzu großen Sorgen darüber, was Ihnen die verschiedenen Speicherstatistiken sagen, es sei denn, Sie tauschen. Mit dem Vorbehalt, dass ein immer größer werdender RSS auf eine Art Speicherverlust hindeutet.

Bei einem Java) - Programm ist es weitaus wichtiger, zu beachten, was auf dem Heap passiert. Der gesamte belegte Speicherplatz ist wichtig, und Sie können einige Schritte unternehmen, um dies zu reduzieren. Wichtiger ist, wie viel Zeit Sie mit der Müllabfuhr verbringen und welche Teile des Haufens eingesammelt werden.

Der Zugriff auf die Festplatte (dh auf eine Datenbank) ist teuer und der Speicher ist billig. Wenn Sie einen gegen den anderen tauschen können, tun Sie dies.

591
kdgregory

Es gibt ein bekanntes Problem mit Java und glibc> = 2.10 (enthält Ubuntu> = 10.04, RHEL> = 6).

Die Heilung besteht darin, diese Umgebung festzulegen. Variable:

export MALLOC_ARENA_MAX=4

Wenn Sie Tomcat ausführen, können Sie dies zur Datei Tomcat_HOME/bin/setenv.sh Hinzufügen.

Fügen Sie dies für Docker zu Dockerfile hinzu

ENV MALLOC_ARENA_MAX=4

Es gibt einen IBM Artikel zum Festlegen von 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=en

In diesem Blogeintrag heißt es

es ist bekannt, dass residenter Speicher auf eine Art und Weise kriecht, die einem Speicherverlust oder einer Speicherfragmentierung ähnelt.

Es gibt auch einen offenen JDK-Fehler JDK-8193521 "glibc verschwendet Speicher mit der Standardkonfiguration"

suche nach MALLOC_ARENA_MAX bei Google oder SO für weitere Referenzen.

Möglicherweise möchten Sie auch andere Malloc-Optionen optimieren, um eine geringe Fragmentierung des zugewiesenen Speichers zu erzielen:

# 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
36
Lari Hotari

Der für den Java) - Prozess zugewiesene Speicher entspricht in etwa dem, was ich erwarten würde. Ich hatte ähnliche Probleme beim Ausführen von Java auf eingebettetem/Systeme mit begrenztem Arbeitsspeicher. Ausführen von any Anwendungen mit willkürlichen VM) Beschränkungen oder auf Systemen, die nicht über ausreichende Mengen an Swap verfügen, neigen dazu, zu brechen Dies liegt in der Natur vieler moderner Apps, die nicht für die Verwendung auf Systemen mit begrenzten Ressourcen konzipiert sind.

Sie haben noch einige weitere Optionen, mit denen Sie versuchen können, den Speicherbedarf Ihrer JVM zu begrenzen. Dies kann den virtuellen Speicherbedarf verringern:

-XX: ReservedCodeCacheSize = 32m Größe des reservierten Code-Caches (in Byte) - maximale Größe des Code-Caches. [Solaris 64-Bit, AMD64 und -server x86: 48 m; in 1.5.0_06 und früheren Versionen, Solaris 64-Bit und and64: 1024m]

-XX: MaxPermSize = 64 m Größe der permanenten Generation. [5.0 und neuer: 64-Bit-VMs werden 30% größer skaliert; 1,4 AMD64: 96 m; 1.3.1 -Client: 32m.]

Außerdem sollten Sie -Xmx (maximale Größe des Heapspeichers) auf einen Wert festlegen, der dem Wert tatsächliche maximale Speichernutzung Ihrer Anwendung möglichst nahe kommt. Ich glaube, das Standardverhalten der JVM ist immer noch, double die Heap-Größe jedes Mal, wenn es auf die maximale Größe erweitert. Wenn Sie mit einem 32-Millionen-Heap beginnen und Ihre App einen Spitzenwert von 65 Millionen hat, wächst der Heap am Ende auf 32 Millionen -> 64 Millionen -> 128 Millionen.

Sie können auch versuchen, die VM weniger aggressiv in Bezug auf das Wachsen des Heaps zu machen:

-XX: MinHeapFreeRatio = 40 Minimaler Prozentsatz an freiem Heap nach dem GC, um eine Erweiterung zu vermeiden.

Soweit ich mich erinnere, hatte die Anzahl der geladenen nativen Bibliotheken vor einigen Jahren einen enormen Einfluss auf den minimalen Platzbedarf. Beim Laden von Java.net.Socket wurden mehr als 15 MB hinzugefügt, wenn ich mich richtig erinnere (und wahrscheinlich auch nicht).

9
James Schek

Die Sun-JVM benötigt viel Speicher für HotSpot und wird in den Laufzeitbibliotheken im gemeinsam genutzten Speicher zugeordnet.

Wenn Speicher ein Problem darstellt, sollten Sie eine andere JVM verwenden, die zum Einbetten geeignet ist. IBM hat j9 und es gibt das Open Source "jamvm", das GNU Klassenpfadbibliotheken verwendet. Auch Sun hat die Squeak JVM auf den SunSPOTS, so dass es Alternativen gibt.

Eine Möglichkeit, die Größe des Heapspeichers eines Systems mit begrenzten Ressourcen zu verringern, besteht darin, mit der Variablen -XX: MaxHeapFreeRatio herumzuspielen. Dieser Wert ist normalerweise auf 70 festgelegt. Dies ist der maximale Prozentsatz des Heapspeichers, der frei ist, bevor der GC ihn verkleinert. Wenn Sie einen niedrigeren Wert einstellen, werden Sie z. B. im jvisualvm-Profiler feststellen, dass normalerweise ein kleinerer Heap-Sice für Ihr Programm verwendet wird.

BEARBEITEN: Um kleine Werte für -XX: MaxHeapFreeRatio festzulegen, müssen Sie auch -XX: MinHeapFreeRatio festlegen, z

Java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2: Es wurde ein Beispiel für eine reale Anwendung hinzugefügt, die dieselbe Aufgabe startet und ausführt, eine mit Standardparametern und eine mit 10 und 25 als Parametern. Ich habe keinen wirklichen Geschwindigkeitsunterschied bemerkt, obwohl Java in letzterem Beispiel theoretisch mehr Zeit benötigen sollte, um den Heap zu vergrößern.

Default parameters

Am Ende beträgt der maximale Heap 905, der verwendete Heap 378

MinHeap 10, MaxHeap 25

Am Ende beträgt der maximale Heap 722, der verwendete Heap 378

Dies hat tatsächlich einige Nachteile, da unsere Anwendung auf einem Remotedesktopserver ausgeführt wird und viele Benutzer sie möglicherweise gleichzeitig ausführen.

3
runholen

Nur ein Gedanke, aber Sie können den Einfluss von a ulimit -v Option .

Dies ist keine tatsächliche Lösung, da der für alle Prozesse verfügbare Adressraum begrenzt wird. Auf diese Weise können Sie jedoch das Verhalten Ihrer Anwendung mit Einschränkungen überprüfen virtueller Speicher.

3
VonC

Suns Java 1.4 hat die folgenden Argumente, um die Speichergröße zu steuern:

-Xmsn Geben Sie die Anfangsgröße des Speicherzuordnungspools in Byte an. Dieser Wert muss ein Vielfaches von 1024 sein, das größer als 1 MB ist. Fügen Sie den Buchstaben k oder K hinzu, um Kilobyte anzuzeigen, oder m oder M, um Megabyte anzuzeigen. Der Standardwert ist 2 MB. Beispiele:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn Geben Sie die maximale Größe des Speicherzuordnungspools in Byte an. Dieser Wert muss ein Vielfaches von 1024 größer als 2 MB sein. Fügen Sie den Buchstaben k oder K hinzu, um Kilobyte anzuzeigen, oder m oder M, um Megabyte anzuzeigen. Der Standardwert ist 64 MB. Beispiele:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://Java.Sun.com/j2se/1.4.2/docs/tooldocs/windows/Java.html

Java 5 und 6 haben noch etwas mehr. Siehe http://Java.Sun.com/javase/technologies/hotspot/vmoptions.jsp

1
Paul Tomblin

Nein, Sie können die von der VM benötigte Speichermenge nicht konfigurieren. Beachten Sie jedoch, dass dies ein virtueller Speicher ist, der nicht resident ist, sodass er nur dort verbleibt, ohne Schaden zu nehmen, wenn er nicht tatsächlich verwendet wird.

Alternativ können Sie eine andere JVM als die Sun-Version mit geringerem Speicherbedarf ausprobieren, was ich hier jedoch nicht empfehlen kann.

0
Marko