JVM相关 - 深入理解 System.gc()(下)

简介: JVM相关 - 深入理解 System.gc()(下)

2. ZGC 的处理

直接不处理,不支持通过 System.gc() 触发 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 的处理

Shenandoah 的处理和 G1 GC 的类似,先判断是不是用户明确触发的 GC,然后通过 DisableExplicitGC 这个 JVM 参数判断是否可以 GC(其实这个是多余的,可以去掉,因为外层JVM_ENTRY_NO_ENV(void, JVM_GC(void))已经处理这个状态位了)。如果可以,则请求 GC,阻塞等待 GC 请求被处理。然后根据 ExplicitGCInvokesConcurrent 这个 JVM 参数决定是默认 GC (轻量并行 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;
  }
}


System.gc() 相关的 JVM 参数


1. DisableExplicitGC

说明:是否禁用显式 GC,默认是不禁用的。对于 Shenandoah GC,显式 GC 包括:GCCause::_java_lang_system_gcGCCause::_dcmd_gc_runGCCause::_jvmti_force_gcGCCause::_heap_inspectionGCCause::_heap_dump,对于其他 GC,仅仅限制GCCause::_java_lang_system_gc

默认:false

举例:如果想禁用显式 GC:-XX:+DisableExplicitGC


2. ExplicitGCInvokesConcurrent

说明:对于显式 GC,是执行轻量并行 GC (YoungGC)还是 FullGC,如果为 true 则是执行轻量并行 GC (YoungGC),false 则是执行 FullGC

默认:false

举例:启用的话指定:-XX:+ExplicitGCInvokesConcurrent

其实,在设计上有人提出(参考链接)想将 ExplicitGCInvokesConcurrent 改为 true。但是目前并不是所有的 GC 都可以在轻量并行 GC 对 Java 所有内存区域进行回收,有些时候必须通过 FullGC。所以,目前这个参数还是默认为 false


3. 已过期的 ExplicitGCInvokesConcurrentAndUnloads 和使用 ClassUnloadingWithConcurrentMark 替代

如果显式 GC采用轻量并行 GC,那么无法执行 Class Unloading(类卸载),如果启用了类卸载功能,可能会有异常。所以通过这个状态位来标记在显式 GC时,即使采用轻量并行 GC,也要扫描进行类卸载。 ExplicitGCInvokesConcurrentAndUnloads目前已经过期了,用ClassUnloadingWithConcurrentMark替代

参考BUG-JDK-8170388


如何灵活可控的主动触发各种 GC?


答案是通过 WhiteBox API。但是这个不要在生产上面执行,仅仅用来测试 JVM 还有学习 JVM 使用。WhiteBox API 是 HotSpot VM 自带的白盒测试工具,将内部的很多核心机制的 API 暴露出来,用于白盒测试 JVM,压测 JVM 特性,以及辅助学习理解 JVM 并调优参数。WhiteBox API 是 Java 7 引入的,目前 Java 8 LTS 以及 Java 11 LTS(其实是 Java 9+ 以后的所有版本,这里只关心 LTS 版本,Java 9 引入了模块化所以 WhiteBox API 有所变化)都是有的。但是默认这个 API 并没有编译在 JDK 之中,但是他的实现是编译在了 JDK 里面了。所以如果想用这个 API,需要用户自己编译需要的 API,并加入 Java 的 BootClassPath 并启用 WhiteBox API。下面我们来用 WhiteBox API 来主动触发各种 GC。

1. 编译 WhiteBox API

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 启动程序。其中前三个 Flag 表示启用 WhiteBox API,最后一个表示打印 GC info 级别的日志到控制台。

我的输出:

[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
相关文章
|
9月前
|
算法 Java API
Java虚拟机System.gc()解析
对于Java语言来说是不用刻意手动去释放内存,同时,也尽可能不需要手动去干预Java虚拟机的GC行为。在本篇文章中,我们试图从多个方面去解析有关System.gc()API调用的最常见问题。希望对需要了解这块技术的朋友有所帮助。
139 0
|
9月前
|
存储 Java C语言
JVM之GC日志解读
JVM之GC日志解读
|
11月前
|
Java
JVM-08垃圾收集Garbage Collection【GC常用参数】
JVM-08垃圾收集Garbage Collection【GC常用参数】
46 0
调用 System.gc() 后究竟发生了什么?
调用 System.gc() 后究竟发生了什么?
|
存储 监控 算法
System.gc()与Runtime.gc()的区别
System.gc()与Runtime.gc()的区别
114 0
|
Java
Java Finalize和System.gc方法
Java Finalize和System.gc方法
80 0
|
消息中间件 Java 测试技术
JVM相关 - 深入理解 System.gc()
本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的 我们经常在面试中询问 System.gc() 究竟会不会 立刻 触发 Full GC ,网上也有很多人给出了答案,但是这些答案都有些过时了。本文基于最新的信息 Java 的下一个即将发布的 LTS 版本 Java 17(ea)的源代码,深入解析 System.gc() 背后的故事。
System类以及垃圾回收gc()使用详情
System类以及垃圾回收gc()使用详情
|
消息中间件 Java 编译器
JVM相关 - 深入理解 System.gc()(上)
JVM相关 - 深入理解 System.gc()(上)
|
消息中间件 Java 编译器
为什么需要 System.gc() ?
为什么需要 System.gc() ?

热门文章

最新文章