Comprensión en profundidad relacionada con JVM de System.gc ()

Este artículo se basa en Java 17-ea, pero el diseño relacionado es aproximadamente el mismo después de Java 11

A menudo preguntamos en las entrevistas si System.gc ()   activará  Full GC inmediatamente  , y muchas personas en Internet han dado respuestas, pero estas respuestas están un poco desactualizadas. Este artículo se basa en la información más reciente sobre el código fuente de Java 17 (ea) de la próxima versión LTS de Java, un análisis en profundidad de la historia detrás de System.gc ().

Comprensión en profundidad relacionada con JVM de System.gc ()

 

¿Por qué se necesita System.gc ()?

1. El marco que usa y administra la memoria fuera del montón requiere un mecanismo de GC completo para activar la recuperación de la memoria fuera del montón.

La memoria JVM, no solo la memoria de pila, sino también muchos otros bloques, puede ver a través del seguimiento de memoria nativa:

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: memoria de pila, es decir, la memoria del tamaño máximo de pila limitada por -Xmx.
  • Clase: la información de método similar cargada es en realidad metaespacio, que contiene dos partes: una es metadatos, que está limitada por el tamaño máximo de -XX: MaxMetaspaceSize, y la otra es el espacio de clases, que está limitado por el tamaño máximo de -XX : CompressedClassSpaceSize
  • Subproceso: El subproceso y la pila de subprocesos ocupan memoria. El tamaño de cada pila de subprocesos está limitado por -Xss, pero el tamaño total no está limitado.
  • Código: el código después de la compilación JIT justo a tiempo (optimizado por el compilador C1 C2) ocupa memoria, que está limitada por -XX: ReservedCodeCacheSize
  • GC: La recolección de basura ocupa memoria, como la CardTable necesaria para la recolección de basura, el número de marcas, el registro de división de área y la marca GC Root, etc., todos requieren memoria. Esto no tiene restricciones y generalmente no es muy grande.
  • Compilador: la memoria ocupada por el código y la marca del compilador C1 C2 en sí no está limitada y generalmente no es muy grande
  • Interno: análisis de línea de comandos, memoria utilizada por JVMTI, esto no está limitado, generalmente no es muy grande
  • Símbolo: el tamaño ocupado por el grupo de constantes, el grupo de constantes de cadena está limitado por el número de -XX: StringTableSize, y el tamaño total de la memoria no está limitado
  • Seguimiento de memoria nativa: El tamaño de la memoria que ocupa la colección de memoria en sí. Si la colección no está activada (entonces no verá esto, jaja), no estará ocupada. Esto no es limitado y generalmente no es muy grande
  • Arena Chunk: Toda la memoria asignada por el método de arena, esto no está limitado, generalmente no es muy grande
  • Tracing: La memoria ocupada por todas las adquisiciones, si JFR está habilitado, es principalmente la memoria ocupada por JFR. Esto no es limitado, generalmente no es muy grande.
  • Logging, Arguments, Module, Synchronizer, Safepoint, Other, generalmente no nos importan.

Además del uso de memoria registrado por Native Memory Tracking, hay dos tipos de memoria   que no están registrados por Native Memory Tracking , es decir:

  • Búfer directo: memoria directa
  • MMap Buffer: memoria de mapeo de archivos

Además de la memoria del montón, alguna otra memoria también requiere GC. Por ejemplo: MetaSpace, CodeCache, Direct Buffer, MMap Buffer, etc. En las primeras JVM anteriores a Java 8, el mecanismo para este reciclaje de memoria no era perfecto.En muchos casos, se requería que  FullGC  escaneara todo el montón para determinar qué memoria en estas áreas se puede reciclar.

Hay algunos marcos que usan y administran mucho estos espacios fuera del montón. Por ejemplo, netty usa Direct Buffer, Kafka y RocketMQ usan Direct Buffer y MMap Buffer. Todos solicitan una parte de la memoria del sistema por adelantado, y luego la administran y usan. Cuando el espacio sea insuficiente, continúe aplicando al sistema y habrá contracción. Por ejemplo, netty, después de que el búfer directo usado alcance el límite de -XX: MaxDirectMemorySize, primero intentará agregar el objeto de referencia inalcanzable a la lista de referencia. El subproceso del demonio interno que se basa en la referencia activa la ejecución () del Limpiador asociado con el método DirectByteBuffer que se puede reciclar. Si la memoria aún es insuficiente, ejecute System.gc () y espere activar gc completo para reclamar los objetos DirectByteBuffer en la memoria del montón para activar la recuperación de memoria fuera del montón. Si aún se excede el límite, se lanza java.lang.OutOfMemoryError .

2. Los programas que utilizan WeakReference y SoftReference requieren la correspondiente recuperación de GC.

Para WeakReference, siempre que se produzca GC, se reciclará Young GC o FullGC. SoftReference solo se reciclará en FullGC. Cuando nuestro programa quiere recuperar activamente estas referencias, necesitamos un método que pueda activar GC, que usa System.gc ().

3. Prueba, al aprender el mecanismo JVM

A veces, para probar y aprender ciertos mecanismos de la JVM, necesitamos dejar que la JVM haga un GC y luego comenzar, que también usará System.gc (). Pero en realidad hay una forma mejor, como verá más adelante.

El principio detrás de System.gc ()

System.gc () en realidad llama a RunTime.getRunTime (). Gc ():

public static void gc() {
    Runtime.getRuntime().gc();
}

Este método es un método nativo:

public native void gc();

Código fuente de JVM correspondiente:

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

En primer lugar, de acuerdo con el estado del parámetro de inicio de JVM DisableExplicitGC, determine si se utilizará GC. Si se requiere GC, diferentes GC tendrán diferentes tratamientos.

1. Procesamiento de GC G1

Si es un GC activado por System.gc (), G1 GC determinará si es el GC predeterminado (GC ligero, YoungGC) o FullGC de acuerdo con el parámetro de JVM ExplicitGCInvokesConcurrent.

Código de referencia 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. Procesamiento ZGC

No se procesa directamente y el GC activado por System.gc () no es compatible.

Código fuente de referencia: 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. Procesamiento Shenandoah GC

El procesamiento de Shenandoah  es similar al  de G1 GC . Primero  juzgue si es un GC activado explícitamente por el usuario  , y luego juzgue si es posible GC a través del parámetro DisableExplicitGC JVM (de hecho, esto es redundante y se puede eliminar porque el layer JVM_ENTRY_NO_ENV (void, JVM_GC (void))) Este bit de estado ha sido procesado). Si es así, solicite GC y bloquee a la espera de que se procese la solicitud de GC. Luego, de acuerdo con el parámetro de JVM ExplicitGCInvokesConcurrent, se determina si es el GC predeterminado (Lightweight Parallel GC, YoungGC) o FullGC.

Código fuente de referencia 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);
  }
}

El flujo de código para solicitar GC es:

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();
  }
}

Para GCCause :: _ java_lang_system_gc, el proceso de ejecución de GC es aproximadamente:

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;
  }
}

Parámetros de JVM relacionados con System.gc ()

1. DisableExplicitGC

Descripción: ya sea para deshabilitar GC explícito  , no está deshabilitado  de forma predeterminada. Para Shenandoah GC, GC  explícito  incluye: GCCause :: _ java_lang_system_gc, GCCause :: _ dcmd_gc_run, GCCause :: _ jvmti_force_gc, GCCause :: _ heap_inspection, GCCause :: _ heap_dump_java, para otras GC_ _java, only_dump_java

Predeterminado: falso

Ejemplo: si desea deshabilitar GC explícito: -XX: + DisableExplicitGC

2. ExplicitGCInvokesConcurrent

Descripción: Para  GC explícito  , ya sea para realizar GC Lightweight Parallel (YoungGC) o FullGC, si es verdadero, es para realizar GC Lightweight Parallel (YoungGC), si es falso, es para realizar FullGC

Predeterminado: falso

Ejemplo: especificar si está habilitado: -XX: + ExplicitGCInvokesConcurrent

De hecho, en el diseño, alguien propuso (enlace de referencia) cambiar ExplicitGCInvokesConcurrent a true. Pero actualmente no todos los GC pueden recuperar todas las áreas de memoria de Java en un GC paralelo ligero y, a veces, deben pasar FullGC. Por lo tanto, actualmente este parámetro sigue siendo falso por defecto.

3. ExplicitGCInvokesConcurrentAndUnloads caducó y usa ClassUnloadingWithConcurrentMark en su lugar

Si el  GC explícito  adopta el GC paralelo ligero, no se puede realizar la descarga de clases. Si la función de descarga de clases está habilitada, puede haber excepciones. Por lo tanto  , cuando un GC explícito está marcado por este bit de estado  , incluso si se utiliza un GC paralelo ligero, se debe escanear para la descarga de clases.

ExplicitGCInvokesConcurrentAndUnloads ha expirado, use ClassUnloadingWithConcurrentMark en su lugar

Referencia BUG-JDK-8170388

¿Cómo activar activamente varios GC de una manera flexible y controlable?

La respuesta es a través de la API de WhiteBox. Pero no haga esto en producción, solo se usa para probar JVM y aprender a usar JVM. WhiteBox API es una herramienta de prueba de caja blanca que viene con HotSpot VM, que expone muchas API de mecanismo central interno para pruebas de caja blanca de JVM, pruebas de estrés de las funciones de JVM y ayuda para aprender y comprender la JVM y los parámetros de ajuste. La API de WhiteBox fue introducida por Java 7. En la actualidad, Java 8 LTS y Java 11 LTS (en realidad, todas las versiones posteriores a Java 9+, solo me importa la versión LTS, Java 9 presenta modularidad, por lo que la API de WhiteBox ha cambiado). Pero esta API no está compilada en JDK por defecto, pero su implementación está compilada en JDK. Entonces, si desea utilizar esta API, debe compilar la API requerida usted mismo, agregar Java BootClassPath y habilitar la API WhiteBox. Usemos la API de WhiteBox para activar activamente varios GC.

1. Compile la API de WhiteBox

Saque el directorio sun en https://github.com/openjdk/jdk/tree/master/test/lib y compílelo en un paquete jar, asumiendo que el nombre es whitebox.jar

2. Escribe un programa de prueba

Agregue whitebox.jar a las dependencias de su proyecto y luego escriba el código

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. Inicie el programa para comprobar el efecto.

Utilice los parámetros de inicio -Xbootclasspath / a: /home/project/whitebox.jar -XX: + UnlockDiagnosticVMOptions -XX: + WhiteBoxAPI -Xlog: gc para iniciar el programa. Los primeros tres indicadores indican que la API de WhiteBox está habilitada y el último indica que los registros del nivel de información de GC se imprimen en la consola.

Mi salida:

[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

Enlace original: http://www.cnblogs.com/zhxdick/p/14449554.html

Si cree que este artículo es útil para usted, puede prestar atención a mi cuenta oficial y responder a la palabra clave [Entrevista] para obtener una compilación de los puntos de conocimiento básicos de Java y un paquete de regalo para la entrevista. Hay más artículos técnicos de productos secos y materiales relacionados para compartir, ¡aprendamos y progresemos juntos!

Supongo que te gusta

Origin blog.csdn.net/weixin_48182198/article/details/114177548
Recomendado
Clasificación