Si vous n'appelez pas System.gc()
, le système lèvera une OutOfMemoryException. Je ne sais pas pourquoi j'ai besoin d'appeler System.gc()
explicitement; la JVM devrait s'appeler gc()
, non? S'il vous plaît donnez votre avis.
Voici mon code de test:
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();
}
}
Comme suit, ajoutez -XX:+PrintGCDetails
pour imprimer les informations du GC; comme vous le voyez, en fait, la JVM essaie de faire une exécution complète du GC, mais échoue; Je ne connais toujours pas la raison. Il est très étrange que si je commente la System.gc();
ligne, le résultat est positif:
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
la source
la source
Réponses:
JVM appellera GC seul, mais dans ce cas, il sera trop peu trop tard. Ce n'est pas seulement GC qui est responsable de l'effacement de la mémoire dans ce cas. Les valeurs de carte sont fortement accessibles et sont effacées par la carte elle-même lorsque certaines opérations y sont appelées.
Voici la sortie si vous activez les événements GC (XX: + PrintGC):
GC n'est pas déclenché jusqu'à la dernière tentative pour mettre de la valeur dans la carte.
WeakHashMap ne peut pas effacer les entrées périmées jusqu'à ce que les clés de mappage se produisent dans une file d'attente de référence. Et les clés de carte n'apparaissent pas dans une file d'attente de référence tant qu'elles n'ont pas été récupérées. L'allocation de mémoire pour la nouvelle valeur de carte est déclenchée avant que la carte n'ait la possibilité de se vider. Lorsque l'allocation de mémoire échoue et déclenche GC, les clés de carte sont collectées. Mais c'est trop peu trop tard - pas assez de mémoire a été libérée pour allouer une nouvelle valeur de carte. Si vous réduisez la charge utile, vous vous retrouverez probablement avec suffisamment de mémoire pour allouer une nouvelle valeur de carte et les entrées périmées seront supprimées.
Une autre solution pourrait être d'encapsuler les valeurs elles-mêmes dans WeakReference. Cela permettra au GC d'effacer les ressources sans attendre que la carte le fasse par lui-même. Voici la sortie:
Bien mieux.
la source
java.util.WeakHashMap.expungeStaleEntries
qui lit la file d'attente de référence et supprime les entrées de la carte, rendant ainsi les valeurs inaccessibles et sujettes à la collecte. Ce n'est qu'après cela qu'une deuxième passe de GC libérera de la mémoire.expungeStaleEntries
est appelé dans un certain nombre de cas comme get / put / size ou à peu près tout ce que vous faites habituellement avec une carte. Voilà le hic.L'autre réponse est en effet correcte, j'ai édité la mienne. Comme un petit addenda,
G1GC
ne présentera pas ce comportement, contrairementParallelGC
; qui est la valeur par défaut sousjava-8
.Que pensez-vous qu'il se passera si je modifie légèrement votre programme en (exécuter sous
jdk-8
avec-Xmx20m
)Cela fonctionnera très bien. Pourquoi donc? Parce qu'il donne à votre programme suffisamment de marge de manœuvre pour que de nouvelles allocations se produisent, avant d'
WeakHashMap
effacer ses entrées. Et l'autre réponse explique déjà comment cela se produit.Maintenant, en
G1GC
, les choses seraient un peu différentes. Quand un si gros objet est alloué (plus de 1/2 Mo en général ), cela s'appelle ahumongous allocation
. Lorsque cela se produit, un GC simultané sera déclenché. Dans le cadre de ce cycle: une jeune collection sera déclenchée et uneCleanup phase
sera initiée qui se chargera de poster l'événement à laReferenceQueue
, afin deWeakHashMap
dégager ses entrées.Donc pour ce code:
que je lance avec jdk-13 (où
G1GC
est la valeur par défaut)Voici une partie des journaux:
Cela fait déjà quelque chose de différent. Il démarre un
concurrent cycle
(fait pendant que votre application est en cours d'exécution), car il y avait unG1 Humongous Allocation
. Dans le cadre de ce cycle simultané, il effectue un cycle GC jeune (qui arrête votre application pendant son exécution)Dans le cadre de ce jeune GC, il efface également des régions gigantesques , voici le défaut .
Vous pouvez maintenant voir que
jdk-13
n'attend pas que les déchets s'accumulent dans l'ancienne région lorsque de très gros objets sont alloués, mais déclenche un cycle GC simultané , qui a sauvé la journée; contrairement à jdk-8.Vous voudrez peut-être lire ce que
DisableExplicitGC
et / ouExplicitGCInvokesConcurrent
dire, couplé avecSystem.gc
et comprendre pourquoi appelerSystem.gc
réellement aide ici.la source
ParalleGC
, j'ai édité et désolé (et merci) de m'avoir prouvé le contraire.-XX:+UseG1GC
faites-le fonctionner en Java 8, tout comme-XX:+UseParallelOldGC
il le fait échouer dans les nouvelles JVM.