JVM related-in-depth understanding of System.gc()

This article is based on Java 17-ea, but the related design is roughly the same after Java 11

We often ask in interviews whether System.gc() will   trigger  Full GC immediately  , and many people on the Internet have given answers, but these answers are a bit out of date. This article is based on the latest information Java's next upcoming LTS version Java 17 (ea) source code, in-depth analysis of the story behind System.gc().

JVM related-in-depth understanding of System.gc()

 

Why is System.gc() needed

1. The framework that uses and manages off-heap memory requires a Full GC mechanism to trigger off-heap memory recovery

JVM memory, not only heap memory, but also many other blocks, you can see through 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: Heap memory, that is, the memory of the maximum heap size limited by -Xmx.
  • Class: The similar method information loaded is actually metaspace, which contains two parts: one is metadata, which is limited by the maximum size of -XX:MaxMetaspaceSize, and the other is class space, which is limited by the maximum size of -XX:CompressedClassSpaceSize
  • Thread: Thread and thread stack occupy memory. The size of each thread stack is limited by -Xss, but the total size is not limited.
  • Code: The code after JIT just-in-time compilation (optimized by C1 C2 compiler) occupies memory, which is limited by -XX:ReservedCodeCacheSize
  • GC: Garbage collection occupies memory, such as the CardTable needed for garbage collection, the number of marks, the area division record, and the mark GC Root, etc., all require memory. This is unrestricted and generally not very big.
  • Compiler: The memory occupied by the code and mark of the C1 C2 compiler itself is not limited and generally not very large
  • Internal: Command line analysis, memory used by JVMTI, this is not limited, generally not very large
  • Symbol: The size occupied by the constant pool, the string constant pool is limited by the number of -XX:StringTableSize, and the total memory size is not limited
  • Native Memory Tracking: The size of the memory occupied by the memory collection itself. If the collection is not turned on (then you won't see this, haha), it will not be occupied. This is not limited and generally not very large
  • Arena Chunk: All the memory allocated by the arena method, this is not limited, generally not very large
  • Tracing: The memory occupied by all acquisitions. If JFR is enabled, it is mainly the memory occupied by JFR. This is not limited, generally not very big
  • Logging, Arguments, Module, Synchronizer, Safepoint, Other, we generally don't care about these.

In addition to the memory usage recorded by Native Memory Tracking, there are two types of memory   that are not recorded by Native Memory Tracking , that is:

  • Direct Buffer: Direct memory
  • MMap Buffer: file mapping memory

In addition to heap memory, some other memory also requires GC. For example: MetaSpace, CodeCache, Direct Buffer, MMap Buffer, etc. In the early JVM before Java 8, the mechanism for these memory recycling was not perfect. In many cases, FullGC was required to   scan the entire heap to determine which memory in these areas can be recycled.

There are some frameworks that use and manage these off-heap space a lot. For example, netty uses Direct Buffer, Kafka and RocketMQ use Direct Buffer and MMap Buffer. They all apply for a piece of memory from the system in advance, and then manage and use it. When the space is insufficient, continue to apply to the system, and there will be shrinkage. For example, netty, after the Direct Buffer used reaches the limit of -XX:MaxDirectMemorySize, it will first try to add the unreachable Reference object to the Reference list. The internal daemon thread that relies on the Reference triggers the run() of the Cleaner associated with the DirectByteBuffer that can be recycled. method. If the memory is still insufficient, execute System.gc() and expect to trigger full gc to reclaim the DirectByteBuffer objects in the heap memory to trigger off-heap memory recovery. If the limit is still exceeded, java.lang.OutOfMemoryError is thrown.

2. Programs using WeakReference and SoftReference require corresponding GC recovery.

For WeakReference, as long as GC occurs, either Young GC or FullGC will be recycled. SoftReference will only be recycled at FullGC. When our program wants to actively reclaim these references, we need a method that can trigger GC, which uses System.gc().

3. Test, when learning JVM mechanism

Sometimes, in order to test and learn certain mechanisms of the JVM, we need to let the JVM do a GC and then start, which will also use System.gc(). But there is actually a better way, as you will see later.

The principle behind System.gc()

System.gc() actually calls RunTime.getRunTime().gc():

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

This method is a native method:

public native void gc();

Corresponding JVM source code:

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

First of all, according to the state of the JVM startup parameter DisableExplicitGC, determine whether GC will be used. If GC is required, different GCs will have different treatments.

1. G1 GC processing

If it is a GC triggered by System.gc(), G1 GC will determine whether it is the default GC (lightweight GC, YoungGC) or FullGC according to the JVM parameter ExplicitGCInvokesConcurrent.

Reference code 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 processing

It is not processed directly, and GC triggered by System.gc() is not supported.

Reference source code: 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 processing

Shenandoah's processing  is similar to  that of G1 GC . First  judge whether it is a GC explicitly triggered by the user  , and then judge whether it is GC possible through the DisableExplicitGC JVM parameter (in fact, this is redundant and can be removed because the outer layer JVM_ENTRY_NO_ENV(void, JVM_GC(void)) ) This status bit has been processed). If so, request GC and block waiting for the GC request to be processed. Then according to the JVM parameter ExplicitGCInvokesConcurrent, it is determined whether it is the default GC (Lightweight Parallel GC, YoungGC) or FullGC.

Reference source code 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);
  }
}

The code flow for requesting GC is:

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

For GCCause::_java_lang_system_gc, the execution process of GC is roughly:

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

System.gc() related JVM parameters

1. DisableExplicitGC

Description: Whether to disable explicit GC  , it is not disabled  by default. For Shenandoah GC,  explicit GC  includes: GCCause::_java_lang_system_gc, GCCause::_dcmd_gc_run, GCCause::_jvmti_force_gc, GCCause::_heap_inspection, GCCause::_heap_dump_java, for other GC::_heap_dump_java, only restrictions

Default: false

Example: If you want to disable explicit GC: -XX:+DisableExplicitGC

2. ExplicitGCInvokesConcurrent

Description: For  explicit GC  , whether to perform Lightweight Parallel GC (YoungGC) or FullGC, if it is true, it is to perform Lightweight Parallel GC (YoungGC), if it is false, it is to perform FullGC

Default: false

Example: Specify if enabled: -XX:+ExplicitGCInvokesConcurrent

In fact, in the design, someone proposed (reference link) to change ExplicitGCInvokesConcurrent to true. But currently not all GCs can reclaim all Java memory areas in lightweight parallel GC, and sometimes they must pass FullGC. Therefore, currently this parameter is still false by default

3. ExplicitGCInvokesConcurrentAndUnloads expired and use ClassUnloadingWithConcurrentMark instead

If the  explicit GC  adopts lightweight parallel GC, then Class Unloading cannot be performed. If the class unloading function is enabled, there may be exceptions. Therefore  , when an explicit GC is marked by this status bit  , even if a lightweight parallel GC is used, it must be scanned for class offloading.

ExplicitGCInvokesConcurrentAndUnloads has expired, use ClassUnloadingWithConcurrentMark instead

Reference BUG-JDK-8170388

How to actively trigger various GCs in a flexible and controllable manner?

The answer is through the WhiteBox API. But don't do this in production, it's only used to test JVM and learn how to use JVM. WhiteBox API is a white box testing tool that comes with HotSpot VM, which exposes many internal core mechanism APIs for white box testing of JVM, stress testing of JVM features, and assisting in learning and understanding JVM and tuning parameters. The WhiteBox API was introduced by Java 7. At present, Java 8 LTS and Java 11 LTS (actually all versions after Java 9+, I only care about the LTS version, Java 9 introduces modularity, so the WhiteBox API has changed). . But this API is not compiled in JDK by default, but its implementation is compiled in JDK. So if you want to use this API, you need to compile the required API yourself, add Java BootClassPath and enable WhiteBox API. Let's use the WhiteBox API to actively trigger various GCs.

1. Compile the WhiteBox API

Take out the sun directory under https://github.com/openjdk/jdk/tree/master/test/lib and compile it into a jar package, assuming the name is whitebox.jar

2. Write a test program

Add whitebox.jar to your project dependencies, and then write the code

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. Start the program to check the effect

Use the startup parameters -Xbootclasspath/a:/home/project/whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xlog:gc to start the program. The first three Flags indicate that the WhiteBox API is enabled, and the last one indicates that logs of GC info level are printed to the console.

My output:

[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

Original link: http://www.cnblogs.com/zhxdick/p/14449554.html

If you think this article is helpful to you, you can pay attention to my official account and reply to the keyword [Interview] to get a compilation of Java core knowledge points and an interview gift package! There are more technical dry goods articles and related materials to share, let's learn and make progress together!

Guess you like

Origin blog.csdn.net/weixin_48182198/article/details/114177548