Эта статья основана на Java 17-ea, но дизайн после Java 11 примерно такой же.
Мы часто спрашиваем в интервьюSystem.gc()
Будет ли оннемедленнокурокFull GC, в Интернете также есть много людей, которые дали ответы, но эти ответы немного устарели. Эта статья основана на исходном коде последней предстоящей версии LTS для Java, Java 17 (ea), чтобы углубиться в историю System.gc() .
Зачем нужен System.gc()
1. Платформа для использования и управления памятью вне кучи требует, чтобы механизм полного GC запускал высвобождение памяти вне кучи.
Память JVM, не только память кучи, но и многие другие блоки, можно увидеть через Native Memory Tracking:
Native Memory Tracking:
Total: reserved=6308603KB, committed=4822083KB
- Java Heap (reserved=4194304KB, committed=4194304KB)
(mmap: reserved=4194304KB, committed=4194304KB)
- Class (reserved=1161041KB, committed=126673KB)
(classes #21662)
( instance classes #20542, array classes #1120)
(malloc=3921KB #64030)
(mmap: reserved=1157120KB, committed=122752KB)
( Metadata: )
( reserved=108544KB, committed=107520KB)
( used=105411KB)
( free=2109KB)
( waste=0KB =0.00%)
( Class space:)
( reserved=1048576KB, committed=15232KB)
( used=13918KB)
( free=1314KB)
( waste=0KB =0.00%)
- Thread (reserved=355251KB, committed=86023KB)
(thread #673)
(stack: reserved=353372KB, committed=84144KB)
(malloc=1090KB #4039)
(arena=789KB #1344)
- Code (reserved=252395KB, committed=69471KB)
(malloc=4707KB #17917)
(mmap: reserved=247688KB, committed=64764KB)
- GC (reserved=199635KB, committed=199635KB)
(malloc=11079KB #29639)
(mmap: reserved=188556KB, committed=188556KB)
- Compiler (reserved=2605KB, committed=2605KB)
(malloc=2474KB #2357)
(arena=131KB #5)
- Internal (reserved=3643KB, committed=3643KB)
(malloc=3611KB #8683)
(mmap: reserved=32KB, committed=32KB)
- Other (reserved=67891KB, committed=67891KB)
(malloc=67891KB #2859)
- Symbol (reserved=26220KB, committed=26220KB)
(malloc=22664KB #292684)
(arena=3556KB #1)
- Native Memory Tracking (reserved=7616KB, committed=7616KB)
(malloc=585KB #8238)
(tracking overhead=7031KB)
- Arena Chunk (reserved=10911KB, committed=10911KB)
(malloc=10911KB)
- Tracing (reserved=25937KB, committed=25937KB)
(malloc=25937KB #8666)
- Logging (reserved=5KB, committed=5KB)
(malloc=5KB #196)
- Arguments (reserved=18KB, committed=18KB)
(malloc=18KB #486)
- Module (reserved=532KB, committed=532KB)
(malloc=532KB #3579)
- Synchronizer (reserved=591KB, committed=591KB)
(malloc=591KB #4777)
- Safepoint (reserved=8KB, committed=8KB)
(mmap: reserved=8KB, committed=8KB)
- Java Heap: куча памяти, т.е.
-Xmx
Ограничьте максимальный размер кучи памяти. - Класс: загруженная информация о классе и методе на самом деле представляет собой метапространство, состоящее из двух частей: одна — метаданные, которая
-XX:MaxMetaspaceSize
Ограничение максимального размера, в дополнение к пространству класса, составляет-XX:CompressedClassSpaceSize
ограничить максимальный размер - Поток: Потоки и стеки потоков занимают память, и на размер каждого стека потоков влияет
-Xss
Ограничение есть, но нет ограничения на общий размер. - Код: Код после JIT-компиляции точно в срок (оптимизированный компилятором C1 C2) занимает память и подвергается
-XX:ReservedCodeCacheSize
ограничение - GC: сборка мусора занимает память, например, CardTable, необходимая для сборки мусора, количество меток, запись разделения области, корень GC метки и т. Д., Все из которых требуют памяти. Это не ограничено и, как правило, не очень велико.
- Компилятор: Память, занимаемая кодом и тегами самого компилятора C1 C2, не ограничена и в целом не очень велика.
- Внутреннее: разбор командной строки, память, используемая JVMTI, не ограничена и, как правило, не очень велика
- Символ: размер, занимаемый пулом констант, на пул строковых констант влияет
-XX:StringTableSize
Количество ограничено, а общий объем памяти не ограничен - Native Memory Tracking:Размер памяти, занимаемый самим сбором памяти.Если сбор не включен (тогда этого не видно, ха-ха), он не будет занят.Это не ограничено и в целом не очень большое .
- Кусок арены: вся память, выделенная ареной, не ограничена и, как правило, не очень велика.
- Трассировка: память, занятая всеми сборами данных. Если JFR включен, это в основном память, занятая JFR. Это не ограничено, как правило, не очень большое
- Логирование, Аргументы, Модуль, Синхронизатор, Сейфпойнт, Другое, обычно нас это не волнует.
В дополнение к использованию памяти, регистрируемому Native Memory Tracking, существует два типа памяти.Отслеживание собственной памяти не регистрируется,Это:
- Прямой буфер: прямая память
- MMap Buffer: память с отображением файлов
В дополнение к памяти кучи, другой памяти, некоторые также нуждаются в GC. Например: MetaSpace, CodeCache, Direct Buffer, MMap Buffer и т. д. В ранних JVM до Java 8 механизм повторного использования памяти не был идеальным, и во многих случаях приходилосьFullGCВся куча сканируется, чтобы определить, какая память в этих регионах может быть освобождена.
Существуют фреймворки, которые интенсивно используют и управляют этим пространством вне кучи. Например, netty использует Direct Buffer, Kafka и RocketMQ используют Direct Buffer и MMap Buffer. Все они заранее запрашивают кусок памяти у системы, а затем управляют и используют его. Когда места недостаточно, продолжайте применять систему, а также будет усадка. Например, netty, когда используемый прямой буфер достигает-XX:MaxDirectMemorySize
После ограничения он сначала попытается добавить недостижимый объект Reference в список Reference, а внутренний поток демона, который опирается на Reference, запускает метод run() Cleaner, связанный с DirectByteBuffer, который можно переработать. Если памяти по-прежнему недостаточно, выполнитеSystem.gc()
, ожидайте срабатыванияfull gc
, чтобы освободить память кучиDirectByteBuffer
объект для запуска восстановления памяти вне кучи, если предел все еще превышен, выброситьjava.lang.OutOfMemoryError
.
2. Программы, использующие WeakReference и SoftReference, требуют соответствующей утилизации GC.
Для WeakReference всякий раз, когда происходит GC, будет собираться Young GC или FullGC. SoftReferences собираются только во время FullGC. Когда наша программа хочет активно перерабатывать эти ссылки, нам нужен метод, который может запускать GC, который используетсяSystem.gc()
.
3. При тестировании изучать механику JVM
Иногда, чтобы протестировать и изучить некоторые механизмы JVM, нам нужно запустить JVM после выполнения GC, который также будет использоватьсяSystem.gc()
. Но на самом деле есть лучший способ, как вы увидите позже.
Обоснование System.gc()
System.gc()
на самом деле звонитRunTime.getRunTime().gc()
:
public static void gc() {
Runtime.getRuntime().gc();
}
Этот метод является родным методом:
public native void gc();
Соответствующий исходный код JVM:
JVM_ENTRY_NO_ENV(void, JVM_GC(void))
JVMWrapper("JVM_GC");
//如果没有将JVM启动参数 DisableExplicitGC 设置为 false,则执行 GC,GC 原因是 System.gc 触发,对应 GCCause::_java_lang_system_gc
if (!DisableExplicitGC) {
Universe::heap()->collect(GCCause::_java_lang_system_gc);
}
JVM_END
Во-первых, в соответствии со статусом параметра запуска JVM DisableExplicitGC определите, будет ли выполняться сборка мусора.Если требуется сборка мусора, разные сборщики мусора будут обрабатываться по-разному.
1. Обработка G1 GC
еслиSystem.gc()
Triggered GC, G1 GC решит, использовать ли GC по умолчанию (легкий GC, YoungGC) или FullGC в соответствии с параметром JVM ExplicitGCInvokesConcurrent.
Код ссылкиg1CollectedHeap.cpp
:
//是否应该并行 GC,也就是较为轻量的 GC,对于 GCCause::_java_lang_system_gc,这里就是判断 ExplicitGCInvokesConcurrent 这个 JVM 是否为 true
if (should_do_concurrent_full_gc(cause)) {
return try_collect_concurrently(cause,
gc_count_before,
old_marking_started_before);
}// 省略其他这里我们不关心的判断分支
else {
//否则进入 full GC
VM_G1CollectFull op(gc_count_before, full_gc_count_before, cause);
VMThread::execute(&op);
return op.gc_succeeded();
}
2. Обработка ZGC
Не обрабатывать напрямую, не поддерживать передачуSystem.gc()
Запустить ГК.
Источник ссылки:zDriver.cpp
void ZDriver::collect(GCCause::Cause cause) {
switch (cause) {
//注意这里的 _wb 开头的 GC 原因,这代表是 WhiteBox 触发的,后面我们会用到,这里先记一下
case GCCause::_wb_young_gc:
case GCCause::_wb_conc_mark:
case GCCause::_wb_full_gc:
case GCCause::_dcmd_gc_run:
case GCCause::_java_lang_system_gc:
case GCCause::_full_gc_alot:
case GCCause::_scavenge_alot:
case GCCause::_jvmti_force_gc:
case GCCause::_metadata_GC_clear_soft_refs:
// Start synchronous GC
_gc_cycle_port.send_sync(cause);
break;
case GCCause::_z_timer:
case GCCause::_z_warmup:
case GCCause::_z_allocation_rate:
case GCCause::_z_allocation_stall:
case GCCause::_z_proactive:
case GCCause::_z_high_usage:
case GCCause::_metadata_GC_threshold:
// Start asynchronous GC
_gc_cycle_port.send_async(cause);
break;
case GCCause::_gc_locker:
// Restart VM operation previously blocked by the GC locker
_gc_locker_port.signal();
break;
case GCCause::_wb_breakpoint:
ZBreakpoint::start_gc();
_gc_cycle_port.send_async(cause);
break;
//对于其他原因,不触发GC,GCCause::_java_lang_system_gc 会走到这里
default:
// Other causes not supported
fatal("Unsupported GC cause (%s)", GCCause::to_string(cause));
break;
}
}
3. Обработка Shenandoah GC
Обработка ШенандоаПохоже на G1 GC,ПервыйОпределить, запускается ли GC явным образом пользователем, а затем используйте параметр DisableExplicitGC JVM, чтобы определить, возможен ли сборщик мусора (на самом деле это избыточно и может быть удалено, поскольку внешний слойJVM_ENTRY_NO_ENV(void, JVM_GC(void))
уже обрабатывать этот бит состояния). Если это так, запросите GC, блокируя ожидание обработки запроса GC. потомВ соответствии с параметром JVM ExplicitGCInvokesConcurrent решите, использовать ли GC по умолчанию (Lightweight Parallel GC, YoungGC) или FullGC..
Справочный исходный кодshenandoahControlThread.cpp
void ShenandoahControlThread::request_gc(GCCause::Cause cause) {
assert(GCCause::is_user_requested_gc(cause) ||
GCCause::is_serviceability_requested_gc(cause) ||
cause == GCCause::_metadata_GC_clear_soft_refs ||
cause == GCCause::_full_gc_alot ||
cause == GCCause::_wb_full_gc ||
cause == GCCause::_scavenge_alot,
"only requested GCs here");
//如果是显式GC(即如果是GCCause::_java_lang_system_gc,GCCause::_dcmd_gc_run,GCCause::_jvmti_force_gc,GCCause::_heap_inspection,GCCause::_heap_dump中的任何一个)
if (is_explicit_gc(cause)) {
//如果没有关闭显式GC,也就是 DisableExplicitGC 为 false
if (!DisableExplicitGC) {
//请求 GC
handle_requested_gc(cause);
}
} else {
handle_requested_gc(cause);
}
}
Поток кода для запроса GC:
void ShenandoahControlThread::handle_requested_gc(GCCause::Cause cause) {
MonitorLocker ml(&_gc_waiters_lock);
//获取当前全局 GC id
size_t current_gc_id = get_gc_id();
//因为要进行 GC ,所以将id + 1
size_t required_gc_id = current_gc_id + 1;
//直到当前全局 GC id + 1 为止,代表 GC 执行了
while (current_gc_id < required_gc_id) {
//设置 gc 状态位,会有其他线程扫描执行 gc
_gc_requested.set();
//记录 gc 原因,根据不同原因有不同的处理策略,我们这里是 GCCause::_java_lang_system_gc
_requested_gc_cause = cause;
//等待 gc 锁对象 notify,代表 gc 被执行并完成
ml.wait();
current_gc_id = get_gc_id();
}
}
заGCCause::_java_lang_system_gc
, поток выполнения GC, вероятно, таков:
bool explicit_gc_requested = _gc_requested.is_set() && is_explicit_gc(_requested_gc_cause);
//省略一些代码
else if (explicit_gc_requested) {
cause = _requested_gc_cause;
log_info(gc)("Trigger: Explicit GC request (%s)", GCCause::to_string(cause));
heuristics->record_requested_gc();
// 如果 JVM 参数 ExplicitGCInvokesConcurrent 为 true,则走默认轻量 GC
if (ExplicitGCInvokesConcurrent) {
policy->record_explicit_to_concurrent();
mode = default_mode;
// Unload and clean up everything
heap->set_unload_classes(heuristics->can_unload_classes());
} else {
//否则,执行 FullGC
policy->record_explicit_to_full();
mode = stw_full;
}
}
Аргументы JVM, связанные с System.gc()
1. DisableExplicitGC
инструкция: отключить лиЯвный GC, не отключен по умолчанию. Для Шенандоа ГК,Явный GCвключают:GCCause::_java_lang_system_gc
,GCCause::_dcmd_gc_run
,GCCause::_jvmti_force_gc
,GCCause::_heap_inspection
,GCCause::_heap_dump
, для других GC только ограничениеGCCause::_java_lang_system_gc
дефолт: ложный
Пример: Если вы хотите отключить явный GC:-XX:+DisableExplicitGC
2. ExplicitGCInvokesConcurrent
инструкция:заЯвный GC, следует ли выполнять упрощенный параллельный сборщик мусора (YoungGC) или полный сборщик мусора, еслиtrue — выполнять облегченный параллельный GC (YoungGC), false — выполнять FullGC
дефолт: ложный
Пример: Если включено, укажите:-XX:+ExplicitGCInvokesConcurrent
Собственно, так и было предложено в конструкции (Ссылка на ссылку) хотите изменить ExplicitGCInvokesConcurrent на true. Но в настоящее время не все сборщики мусора могут освободить все области памяти Java в упрощенном параллельном сборщике мусора и иногда должны пройти FullGC. Итак, на данный момент этот параметр по умолчанию по-прежнему имеет значение false.
3. ExplicitGCInvokesConcurrentAndUnloads с истекшим сроком действия и вместо этого используйте ClassUnloadingWithConcurrentMark
еслиЯвный GCПри облегченном параллельном сборщике мусора выгрузка классов невозможна, и могут быть исключения, если выгрузка классов включена. Таким образом, через этот бит состояния, чтобы отметить вЯвный GC, даже с упрощенным параллельным сборщиком мусора сканируйте на предмет выгрузки классов.ExplicitGCInvokesConcurrentAndUnloads
Срок действия истек, используйтеClassUnloadingWithConcurrentMark
альтернатива
Ссылаться наBUG-JDK-8170388
Как гибко и контролируемо запускать различные сборщики мусора?
Ответ через WhiteBox API. Но не делайте этого на производстве, просто используйте его для тестирования JVM и изучения того, как использовать JVM. WhiteBox API — это инструмент тестирования белого ящика, который поставляется с HotSpot VM. Он предоставляет API многих внутренних основных механизмов для тестирования JVM белого ящика, стресс-тестирования функций JVM и помогает научиться понимать JVM и параметры настройки. API WhiteBox был представлен в Java 7. В настоящее время Java 8 LTS и Java 11 LTS (фактически все версии после Java 9+, здесь речь идет только о версии LTS, в Java 9 введена модульность, поэтому WhiteBox API изменился). . Но по умолчанию этот API не компилируется в JDK, а его реализация компилируется в JDK. Поэтому, если вы хотите использовать этот API, вам нужно скомпилировать нужный вам API, добавить BootClassPath Java и включить API WhiteBox. Давайте воспользуемся API WhiteBox для активного запуска различных сборщиков мусора.
1. Скомпилируйте API WhiteBox
будетhttps://github.com/openjdk/jdk/tree/master/test/lib
под дорожкойsun
Каталог вынимается и компилируется в jar-пакет, предполагается, что имяwhitebox.jar
2. Напишите тестовую программу
будетwhitebox.jar
Добавьте зависимости в свой проект, а затем напишите код
public static void main(String[] args) throws Exception {
WhiteBox whiteBox = WhiteBox.getWhiteBox();
//执行young GC
whiteBox.youngGC();
System.out.println("---------------------------------");
whiteBox.fullGC();
//执行full GC
whiteBox.fullGC();
//保持进程不退出,保证日志打印完整
Thread.currentThread().join();
}
3. Запустите программу для просмотра эффекта
Использовать параметры запуска-Xbootclasspath/a:/home/project/whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xlog:gc
стартовая программа. Первые три флага указывают на то, что WhiteBox API включен, а последний указывает на то, что журнал информационного уровня сборщика мусора выводится на консоль.
мой вывод:
[0.036s][info][gc] Using G1
[0.048s][info][gc,init] Version: 17-internal+0-adhoc.Administrator.jdk (fastdebug)
[0.048s][info][gc,init] CPUs: 16 total, 16 available
[0.048s][info][gc,init] Memory: 16304M
[0.048s][info][gc,init] Large Page Support: Disabled
[0.048s][info][gc,init] NUMA Support: Disabled
[0.048s][info][gc,init] Compressed Oops: Enabled (32-bit)
[0.048s][info][gc,init] Heap Region Size: 1M
[0.048s][info][gc,init] Heap Min Capacity: 512M
[0.048s][info][gc,init] Heap Initial Capacity: 512M
[0.048s][info][gc,init] Heap Max Capacity: 512M
[0.048s][info][gc,init] Pre-touch: Disabled
[0.048s][info][gc,init] Parallel Workers: 13
[0.048s][info][gc,init] Concurrent Workers: 3
[0.048s][info][gc,init] Concurrent Refinement Workers: 13
[0.048s][info][gc,init] Periodic GC: Disabled
[0.049s][info][gc,metaspace] CDS disabled.
[0.049s][info][gc,metaspace] Compressed class space mapped at: 0x0000000100000000-0x0000000140000000, reserved size: 1073741824
[0.049s][info][gc,metaspace] Narrow klass base: 0x0000000000000000, Narrow klass shift: 3, Narrow klass range: 0x140000000
[1.081s][info][gc,start ] GC(0) Pause Young (Normal) (WhiteBox Initiated Young GC)
[1.082s][info][gc,task ] GC(0) Using 12 workers of 13 for evacuation
[1.089s][info][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.5ms
[1.089s][info][gc,phases ] GC(0) Merge Heap Roots: 0.1ms
[1.089s][info][gc,phases ] GC(0) Evacuate Collection Set: 3.4ms
[1.089s][info][gc,phases ] GC(0) Post Evacuate Collection Set: 1.6ms
[1.089s][info][gc,phases ] GC(0) Other: 1.3ms
[1.089s][info][gc,heap ] GC(0) Eden regions: 8->0(23)
[1.089s][info][gc,heap ] GC(0) Survivor regions: 0->2(4)
[1.089s][info][gc,heap ] GC(0) Old regions: 0->0
[1.089s][info][gc,heap ] GC(0) Archive regions: 0->0
[1.089s][info][gc,heap ] GC(0) Humongous regions: 0->0
[1.089s][info][gc,metaspace] GC(0) Metaspace: 6891K(7104K)->6891K(7104K) NonClass: 6320K(6400K)->6320K(6400K) Class: 571K(704K)->571K(704K)
[1.089s][info][gc ] GC(0) Pause Young (Normal) (WhiteBox Initiated Young GC) 7M->1M(512M) 7.864ms
[1.089s][info][gc,cpu ] GC(0) User=0.00s Sys=0.00s Real=0.01s
---------------------------------
[1.091s][info][gc,task ] GC(1) Using 12 workers of 13 for full compaction
[1.108s][info][gc,start ] GC(1) Pause Full (WhiteBox Initiated Full GC)
[1.108s][info][gc,phases,start] GC(1) Phase 1: Mark live objects
[1.117s][info][gc,phases ] GC(1) Phase 1: Mark live objects 8.409ms
[1.117s][info][gc,phases,start] GC(1) Phase 2: Prepare for compaction
[1.120s][info][gc,phases ] GC(1) Phase 2: Prepare for compaction 3.031ms
[1.120s][info][gc,phases,start] GC(1) Phase 3: Adjust pointers
[1.126s][info][gc,phases ] GC(1) Phase 3: Adjust pointers 5.806ms
[1.126s][info][gc,phases,start] GC(1) Phase 4: Compact heap
[1.190s][info][gc,phases ] GC(1) Phase 4: Compact heap 63.812ms
[1.193s][info][gc,heap ] GC(1) Eden regions: 1->0(25)
[1.193s][info][gc,heap ] GC(1) Survivor regions: 2->0(4)
[1.193s][info][gc,heap ] GC(1) Old regions: 0->3
[1.193s][info][gc,heap ] GC(1) Archive regions: 0->0
[1.193s][info][gc,heap ] GC(1) Humongous regions: 0->0
[1.193s][info][gc,metaspace ] GC(1) Metaspace: 6895K(7104K)->6895K(7104K) NonClass: 6323K(6400K)->6323K(6400K) Class: 571K(704K)->571K(704K)
[1.193s][info][gc ] GC(1) Pause Full (WhiteBox Initiated Full GC) 1M->0M(512M) 84.846ms
[1.202s][info][gc,cpu ] GC(1) User=0.19s Sys=0.63s Real=0.11s
Ищите «My Programming Meow» в WeChat, подписывайтесь на официальный аккаунт, чистите каждый день, легко улучшайте свои технологии и получайте различные предложения.