本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的
我们经常在面试中询问 System.gc()
究竟会不会立刻触发 Full GC,网上也有很多人给出了答案,但是这些答案都有些过时了。本文基于最新的 Java 的下一个即将发布的 LTS 版本 Java 17(ea)的源代码,深入解析 System.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)
-Xmx
限制的最大堆大小的内存。-XX:MaxMetaspaceSize
限制最大大小,另外是 class space,被-XX:CompressedClassSpaceSize
限制最大大小-Xss
限制,但是总大小没有限制。-XX:ReservedCodeCacheSize
限制-XX:StringTableSize
个数限制,总内存大小不受限制除了 Native Memory Tracking 记录的内存使用,还有两种内存 Native Memory Tracking 没有记录,那就是:
针对除了堆内存以外,其他的内存,有些也是需要 GC 的。例如:MetaSpace,CodeCache,Direct Buffer,MMap Buffer 等等。早期在 Java 8 之前的 JVM,对于这些内存回收的机制并不完善,很多情况下都需要 FullGC 扫描整个堆才能确定这些区域中哪些内存可以回收。
有一些框架,大量使用并管理了这些堆外空间。例如 netty 使用了 Direct Buffer,Kafka 和 RocketMQ 使用了 Direct Buffer 和 MMap Buffer。他们都是提前从系统申请好一块内存,之后管理起来并使用。
在空间不足时,继续向系统申请,并且也会有缩容。例如 netty,在使用的 Direct Buffer 达到-XX:MaxDirectMemorySize
的限制之后,则会先尝试将不可达的Reference对象加入Reference链表中,依赖Reference的内部守护线程触发可以被回收DirectByteBuffer关联的Cleaner的run()方法。
如果内存还是不足, 则执行System.gc()
,期望触发full gc
,来回收堆内存中的DirectByteBuffer
对象来触发堆外内存回收,如果还是超过限制,则抛出java.lang.OutOfMemoryError
.
对于 WeakReference,只要发生 GC,无论是 Young GC 还是 FullGC 就会被回收。SoftReference 只有在 FullGC 的时候才会被回收。当我们程序想主动对于这些引用进行回收的时候,需要能触发 GC 的方法,这就用到了System.gc()
。
有些时候,我们为了测试,学习 JVM 的某些机制,需要让 JVM 做一次 GC 之后开始,这也会用到System.gc()
。但是其实有更好的方法,后面你会看到。
System.gc()
实际上调用的是RunTime.getRunTime().gc()
:
public static void gc() {
Runtime.getRuntime().gc();
}
这个方法是一个 native 方法:
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
首先,根据 DisableExplicitGC 这个 JVM 启动参数的状态,确定是否会 GC,如果需要 GC,不同 GC 会有不同的处理。
如果是 System.gc()
触发的 GC,G1 GC 会根据 ExplicitGCInvokesConcurrent 这个 JVM 参数决定是默认 GC (轻量 GC,YoungGC)还是 FullGC。
参考代码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();
}
直接不处理,不支持通过 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;
}
}
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;
}
}
说明:是否禁用显式 GC,默认是不禁用的。对于 Shenandoah 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
默认:false
举例:如果想禁用显式 GC:-XX:+DisableExplicitGC
说明:对于显式 GC,是执行轻量并行 GC (YoungGC)还是 FullGC,如果为 true 则是执行轻量并行 GC (YoungGC),false 则是执行 FullGC
默认:false
举例:启用的话指定:-XX:+ExplicitGCInvokesConcurrent
其实,在设计上有人提出(参考链接)想将 ExplicitGCInvokesConcurrent 改为 true。但是目前并不是所有的 GC 都可以在轻量并行 GC 对 Java 所有内存区域进行回收,有些时候必须通过 FullGC。所以,目前这个参数还是默认为 false
如果显式 GC采用轻量并行 GC,那么无法执行 Class Unloading(类卸载),如果启用了类卸载功能,可能会有异常。所以通过这个状态位来标记在显式 GC时,即使采用轻量并行 GC,也要扫描进行类卸载。ExplicitGCInvokesConcurrentAndUnloads
目前已经过期了,用ClassUnloadingWithConcurrentMark
替代
参考BUG-JDK-8170388
答案是通过 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
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/NjNGNotGt5BKCqGLjZmALg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。