Если не вызывать System.gc()
, система выдаст исключение OutOfMemoryException. Я не знаю, почему мне нужно звонить System.gc()
явно; JVM должна называть gc()
себя, верно? Пожалуйста, порекомендуйте.
Вот мой тестовый код:
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();
}
}
Как следует, добавьте, -XX:+PrintGCDetails
чтобы распечатать информацию GC; как вы видите, на самом деле JVM пытается выполнить полный запуск GC, но терпит неудачу; Я до сих пор не знаю причину. Очень странно, что если я раскомментирую System.gc();
строку, результат будет положительным:
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
Доминик Пэн
источник
источник
Ответы:
JVM сама вызовет GC, но в этом случае будет слишком поздно. В этом случае ответственность за очистку памяти несет не только GC. Значения карты достижимы и очищаются самой картой, когда на ней вызываются определенные операции.
Вот вывод, если вы включите события GC (XX: + PrintGC):
GC не срабатывает до последней попытки поместить значение в карту.
WeakHashMap не может очистить устаревшие записи, пока в карте ссылок не появятся ключи карты. И ключи карты не появляются в ссылочной очереди до тех пор, пока они не будут удалены. Выделение памяти для нового значения карты происходит до того, как карта сможет очистить себя. Когда распределение памяти не удается и запускает GC, ключи карты собираются. Но слишком поздно, слишком поздно - недостаточно памяти было освобождено для выделения нового значения карты. Если вы уменьшите полезную нагрузку, у вас, вероятно, будет достаточно памяти для выделения нового значения карты, и устаревшие записи будут удалены.
Другое решение может заключаться в переносе самих значений в WeakReference. Это позволит GC очищать ресурсы, не дожидаясь, пока карта сделает это самостоятельно. Вот вывод:
Намного лучше.
источник
java.util.WeakHashMap.expungeStaleEntries
который читает справочную очередь и удаляет записи с карты, таким образом делая значения недоступными и подлежащими сбору. Только после этого второй проход GC освободит память.expungeStaleEntries
вызывается в ряде случаев, таких как get / put / size или почти во всех случаях, которые вы обычно делаете с картой. Это подвох.Другой ответ действительно правильный, я отредактировал мой. Как небольшое дополнение,
G1GC
не будет демонстрировать это поведение, в отличие отParallelGC
; который по умолчанию подjava-8
.Как вы думаете , что произойдет , если я немного изменить свою программу (бежать под
jdk-8
с-Xmx20m
)Это будет работать просто отлично. Это почему? Потому что это дает вашей программе достаточно места для новых распределений, прежде чем
WeakHashMap
очищать свои записи. А другой ответ уже объясняет, как это происходит.Теперь
G1GC
все будет немного иначе. Когда выделяется такой большой объект ( обычно более 1/2 МБ ), это называетсяhumongous allocation
. Когда это произойдет, одновременный GC будет запущен. Как часть этого цикла: будет запущена молодая коллекция, иCleanup phase
будет инициирована операция, которая позаботится о публикации события вReferenceQueue
, чтобыWeakHashMap
очистить его записи.Итак, для этого кода:
что я запускаю с JDK-13 (где
G1GC
по умолчанию)Вот часть логов:
Это уже делает что-то другое. Он запускает
concurrent cycle
(сделано во время работы вашего приложения), потому что былG1 Humongous Allocation
. Как часть этого параллельного цикла, он выполняет молодой цикл GC (который останавливает ваше приложение во время работы)Как часть этого молодого GC, он также очищает огромные области , вот недостаток .
Теперь вы можете видеть, что
jdk-13
не ожидает накопления мусора в старом регионе, когда выделяются действительно большие объекты, но запускает параллельный цикл GC, который спас день; в отличие от JDK-8.Возможно, вы захотите прочитать, что
DisableExplicitGC
и / илиExplicitGCInvokesConcurrent
имеется в виду, в сочетании сSystem.gc
пониманием того, почему наSystem.gc
самом деле помогает звонок .источник
ParalleGC
, я отредактировал и извиняюсь (и спасибо), что доказал, что я не прав.-XX:+UseG1GC
сделайте так, чтобы он работал в Java 8, точно так же, как-XX:+UseParallelOldGC
и в новых JVM.