Wenn Sie nicht aufrufen System.gc()
, löst das System eine OutOfMemoryException aus. Ich weiß nicht, warum ich System.gc()
explizit anrufen muss; Die JVM sollte sich gc()
selbst nennen , oder? Bitte beraten.
Folgendes ist mein Testcode:
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, int[]> hm = new WeakHashMap<>();
int i = 0;
while(true) {
Thread.sleep(1000);
i++;
String key = new String(new Integer(i).toString());
System.out.println(String.format("add new element %d", i));
hm.put(key, new int[1024 * 10000]);
key = null;
//System.gc();
}
}
Fügen Sie wie folgt hinzu -XX:+PrintGCDetails
, um die GC-Informationen auszudrucken. Wie Sie sehen, versucht die JVM tatsächlich, einen vollständigen GC-Lauf durchzuführen, schlägt jedoch fehl. Ich kenne den Grund immer noch nicht. Es ist sehr seltsam, dass System.gc();
das Ergebnis positiv ist, wenn ich die Zeile auskommentiere :
add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
[GC (Allocation Failure) --[PSYoungGen: 48344K->48344K(59904K)] 168344K->168352K(196608K), 0.0090913 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 48344K->41377K(59904K)] [ParOldGen: 120008K->120002K(136704K)] 168352K->161380K(196608K), [Metaspace: 5382K->5382K(1056768K)], 0.0380767 secs] [Times: user=0.09 sys=0.03, real=0.04 secs]
[GC (Allocation Failure) --[PSYoungGen: 41377K->41377K(59904K)] 161380K->161380K(196608K), 0.0040596 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 41377K->41314K(59904K)] [ParOldGen: 120002K->120002K(136704K)] 161380K->161317K(196608K), [Metaspace: 5382K->5378K(1056768K)], 0.0118884 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at test.DeadLock.main(DeadLock.java:23)
Heap
PSYoungGen total 59904K, used 42866K [0x00000000fbd80000, 0x0000000100000000, 0x0000000100000000)
eden space 51712K, 82% used [0x00000000fbd80000,0x00000000fe75c870,0x00000000ff000000)
from space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
to space 8192K, 0% used [0x00000000ff000000,0x00000000ff000000,0x00000000ff800000)
ParOldGen total 136704K, used 120002K [0x00000000f3800000, 0x00000000fbd80000, 0x00000000fbd80000)
object space 136704K, 87% used [0x00000000f3800000,0x00000000fad30b90,0x00000000fbd80000)
Metaspace used 5409K, capacity 5590K, committed 5760K, reserved 1056768K
class space used 576K, capacity 626K, committed 640K, reserved 1048576K
java
java-8
garbage-collection
out-of-memory
weak-references
Dominic Peng
quelle
quelle
Antworten:
JVM ruft GC alleine an, aber in diesem Fall ist es zu spät. In diesem Fall ist nicht nur GC für das Löschen des Speichers verantwortlich. Kartenwerte sind stark erreichbar und werden von der Karte selbst gelöscht, wenn bestimmte Vorgänge darauf aufgerufen werden.
Hier ist die Ausgabe, wenn Sie GC-Ereignisse aktivieren (XX: + PrintGC):
GC wird erst beim letzten Versuch ausgelöst, einen Wert in die Karte einzufügen.
WeakHashMap kann veraltete Einträge erst löschen, wenn Kartenschlüssel in einer Referenzwarteschlange vorkommen. Und die Kartenschlüssel treten erst in einer Referenzwarteschlange auf, wenn sie durch Müll gesammelt wurden. Die Speicherzuweisung für einen neuen Kartenwert wird ausgelöst, bevor die Karte die Möglichkeit hat, sich selbst zu löschen. Wenn die Speicherzuordnung fehlschlägt und GC auslöst, werden Kartenschlüssel gesammelt. Aber es ist zu wenig zu spät - es wurde nicht genügend Speicher freigegeben, um einen neuen Kartenwert zuzuweisen. Wenn Sie die Nutzlast reduzieren, verfügen Sie wahrscheinlich über genügend Speicher, um neue Kartenwerte zuzuweisen, und veraltete Einträge werden entfernt.
Eine andere Lösung könnte darin bestehen, Werte selbst in WeakReference zu verpacken. Auf diese Weise kann GC Ressourcen löschen, ohne darauf warten zu müssen, dass die Karte dies selbst tut. Hier ist die Ausgabe:
Viel besser.
quelle
java.util.WeakHashMap.expungeStaleEntries
die die Referenzwarteschlange liest und Einträge aus der Karte entfernt, wodurch Werte nicht erreichbar sind und erfasst werden müssen. Erst danach wird durch den zweiten Durchgang des GC Speicherplatz frei.expungeStaleEntries
wird in einer Reihe von Fällen aufgerufen, z. B. get / put / size oder so ziemlich alles, was Sie normalerweise mit einer Karte tun. Das ist der Haken.Die andere Antwort ist in der Tat richtig, ich habe meine bearbeitet. Als kleines Addendum
G1GC
wird dieses Verhalten im Gegensatz zu nicht zeigenParallelGC
; Das ist die Standardeinstellung unterjava-8
.Was denkst du wird passieren, wenn ich dein Programm leicht ändere auf (laufe mit
jdk-8
mit-Xmx20m
)Es wird gut funktionieren. Warum ist das so? Weil es Ihrem Programm gerade genug Raum zum Atmen gibt, damit neue Zuordnungen stattfinden können, bevor
WeakHashMap
die Einträge gelöscht werden. Und die andere Antwort erklärt bereits, wie das passiert.Jetzt
G1GC
wäre es ein bisschen anders. Wenn ein so großes Objekt zugewiesen wird ( normalerweise mehr als 1/2 MB ), wird dies als a bezeichnethumongous allocation
. In diesem Fall wird eine gleichzeitige GC ausgelöst. Als Teil dieses Zyklus: Es wird eine junge Sammlung ausgelöst und eineCleanup phase
initiiert, die sich darum kümmert, das Ereignis auf dem zu veröffentlichenReferenceQueue
, damitWeakHashMap
seine Einträge gelöscht werden.Also für diesen Code:
dass ich mit jdk-13 laufe (wo
G1GC
ist die Standardeinstellung)Hier ist ein Teil der Protokolle:
Das macht schon etwas anderes. Es startet ein
concurrent cycle
(erledigt, während Ihre Anwendung ausgeführt wird), weil es ein gabG1 Humongous Allocation
. Im Rahmen dieses gleichzeitigen Zyklus wird ein junger GC-Zyklus durchgeführt (der Ihre Anwendung während der Ausführung stoppt ).Als Teil dieses jungen GC räumt es auch humongous Regionen , hier ist der Defekt .
Sie können jetzt sehen, dass
jdk-13
nicht darauf gewartet wird, dass sich in der alten Region Müll ansammelt, wenn wirklich große Objekte zugewiesen werden, sondern ein gleichzeitiger GC-Zyklus ausgelöst wird, der den Tag rettet. im gegensatz zu jdk-8.Vielleicht möchten Sie lesen, was
DisableExplicitGC
und / oderExplicitGCInvokesConcurrent
bedeuten, zusammen mitSystem.gc
und verstehen, warum das Anrufen hierSystem.gc
tatsächlich hilft.quelle
ParalleGC
bearbeitet wurde. Es tut mir leid (und ich danke Ihnen), dass Sie mir das Gegenteil bewiesen haben.-XX:+UseG1GC
Sie ihn also in Java 8 funktionieren, genauso wie-XX:+UseParallelOldGC
er in neuen JVMs fehlschlägt.