Files

202 lines
16 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"topic": "gc-deep-dive",
"type": "single_choice",
"schema_version": "1.0.0",
"generated": "2026-09-03T14:48:48Z",
"questions": [
{
"id": "sc-001",
"type": "single_choice",
"difficulty": 1,
"tags": [
"gc",
"collections"
],
"question": "关于「引用计数」和「可达性分析」两种 GC 判定方法的区别,下列说法正确的是:",
"options": {
"A": "引用计数能从根本上解决循环引用问题,无需额外机制",
"B": "可达性分析从 GC Roots 出发遍历对象图,能处理循环引用",
"C": "引用计数在 Java 中作为 JVM 回收判定的唯一依据",
"D": "可达性分析只能判断对象是否被强引用引用,无法判断软引用"
},
"answer": "B",
"explanation": "A 错:引用计数的致命缺陷恰恰是无法处理循环引用(两个对象互相引用但都无外部引用时,计数恒不为 0 而无法回收)。B 对:可达性分析从 GC Roots 出发,通过引用链遍历对象图,凡是从 Roots 不可达的对象即判定为可回收,循环引用若整体不可达也会被连带回收,这正是 JVM(HotSpot)采用可达性分析而非引用计数的核心原因。C 错:HotSpot 的 GC 判定依据是「可达性分析」而非引用计数;引用计数更难被 JVM 普遍采纳的理由之一就是循环引用与每次赋值上的计数开销(后来引入一些引用计数垃圾回收器仍需配合追踪式收集或循环检测兜底)。D 错:可达性分析同时支持强引用、软引用、弱引用、虚引用的可达性判定,可达性分析以「 StronglyReachable / SoftlyReachable 等」分类描述不同引用类型,并非只能判断强引用。",
"source": null,
"related": []
},
{
"id": "sc-002",
"type": "single_choice",
"difficulty": 2,
"tags": [
"gc"
],
"question": "在「无 GC 的语言」中,下列对应关系错误的是:",
"options": {
"A": "C/C++:手动管理内存(malloc/free、new/delete)",
"B": "Rust:通过所有权(ownership)与借用检查在编译期决定内存释放时机",
"C": "Swift:采用 ARC,在运行时通过引用计数自动释放对象",
"D": "Rust 和 Swift 本质上都依赖运行时 GC 线程进行可达性分析"
},
"answer": "D",
"explanation": "A 对:C/C++ 依赖程序员手动申请(malloc/new)和释放(free/delete)内存,漏释放会产生泄漏,过早释放造成悬垂指针。B 对:Rust 的所有权与借用规则让内存的释放时机在编译期确定,无 GC 且无手动管理,兼得安全与可预测。C 对:Swift 使用 Automatic Reference Counting(ARC),在编译期插入 retain/release,通过引用计数在运行时自动释放,属于非追踪式、无 STW 的回收方式。D 错:Rust 完全不依赖运行时 GC 和可达性分析;Swift 的 ARC 是引用计数而非可达性分析,且均在运行时局部做增减计数而非扫描对象图——正是本题错误项。",
"source": null,
"related": []
},
{
"id": "sc-003",
"type": "single_choice",
"difficulty": 3,
"tags": [
"gc"
],
"question": "关于「GC 的代价」,下列说法不正确的是:",
"options": {
"A": "使用 GC 可能带来约 5%–20% 的内存额外开销,用于元数据、对象头和分配缓冲",
"B": "Stop-The-World(STW)暂停时间主要影响吞吐量,对延迟不敏感场景也要极力避免",
"C": "吞吐量与延迟往往存在取舍:追踪更频繁可降低单次延迟,但可能提高整体 CPU 开销",
"D": "由于释放时机由 GC 决定,销毁时机不可预测的副作用资源(如文件句柄)不应依赖 finalizer"
},
"answer": "B",
"explanation": "A 对:GC 需要对象头标记、卡表、分区状态等元数据以及 TLAB(线程本地分配缓冲)等结构,带来数%~20% 的内存开销。B 错(本题错误项):STW 暂停影响的是「延迟(响应时延)」,即应用线程被冻结的这段时间;吞吐量受影响的是因 GC 而占用的 CPU 总时间。STW 暂停对在线服务、低延迟应用影响显著,不能被简单认为「与延迟关系不大反而主要影响吞吐量」;把两者混淆正是这里要辨析的坑。C 对:吞吐量(完成有用工作的 CPU 占比)与延迟(单次暂停时长)需要权衡:增量/并发收集可以缩短单次暂停但引入更多标记停顿,并增加复杂度。D 对:finalizer 执行时机不确定,不适合管理需要及时释放的系统资源(文件句柄、网络连接),应使用显式 close。",
"source": null,
"related": []
},
{
"id": "sc-004",
"type": "single_choice",
"difficulty": 4,
"tags": [
"gc",
"collections"
],
"question": "比较「标记-清除」「标记-整理(压缩)」「复制」三种基础回收算法,下列说法正确的是:",
"options": {
"A": "标记-清除速度快且不产生内存碎片,适合管理大对象",
"B": "标记-整理不产生碎片,但需要额外的等量内存进行复制",
"C": "复制算法无碎片但代价是空间利用率低(需预留等大半区,通常浪费约一半内存)",
"D": "三种算法都必须依赖 STW 才能完成,无法与并发方案结合"
},
"answer": "C",
"explanation": "A 错:标记-清除速度快、不移动对象,但会在堆中留下大量不连续碎片,导致大对象难以分配、TLAB 等申请饥饿,并不「无碎片」。B 错:标记-整理无碎片且不额外占用等量空间,它通过在堆内移动对象并把存活对象向一端重整,缺点是需要移动和更新引用、成本相对高,而不是靠复制第二片空间——「浪费一半内存」是复制算法的特征,别张冠李戴。C 对:复制算法(如新生代的 From/To 幸存区)把活对象搬到另一半空间,天然紧凑无碎片,但要预留一块等大空间,内存利用率按设计通常接近一半,这就是它浪费空间的地方。D 错:复制算法可以结合「半区复制并发」思路,标记-清除/整理也可通过 SATB、增量更新等手段与并发标记配合(如 CMS、G1),并非只能依赖全量 STW。",
"source": null,
"related": []
},
{
"id": "sc-005",
"type": "single_choice",
"difficulty": 2,
"tags": [
"分代",
"collections"
],
"question": "HotSpot 分代堆中新生代默认采用 Eden + 两块 Survivor(S0/S1)。以下说法正确的是:",
"options": {
"A": "默认情况下 Eden 约占新生代 80%,S0 与 S1 各约占 10%",
"B": "新对象总是先分配到 S0/S1,熬过 Minor GC 后才搬入 Eden",
"C": "Minor GC 会同时清理整个新生代与整个老年代",
"D": "Full GC 通常只回收新生代,不涉及老年代"
},
"answer": "A",
"explanation": "A 对:默认配置下新生代划分为 Eden 与两块 Survivor(S0/S1),Eden 约占 80%,两块 Survivor 各约占 10%,这样一个比例刚好承载『绝大多数对象朝生夕灭』,Eden 负责快速分配。B 错:新对象首先分配到 Eden(配合 TLAB 快速分配),而不是先进 Survivor;对象要熬过 Minor GC 存活、多次幸存后才在幸存区(S0/S1)之间复制并逐步向老年代推进。C 错:Minor GC 是新生代回收,清理范围是 Eden + 幸存区,不包含老年代,老年代回收属于 Major/Full GC。D 错:Full GC 针对整个堆(新生代 + 老年代)做回收与整理,并非只回收新生代。",
"source": null,
"related": []
},
{
"id": "sc-006",
"type": "single_choice",
"difficulty": 3,
"tags": [
"分代"
],
"question": "分代 GC 的「晋升」机制中,下列说法正确的是:",
"options": {
"A": "对象每熬过一次 Minor GC 且幸存,其年龄加 1,年龄默认达到 15 时晋升到老年代",
"B": "只要年龄达到阈值,对象就会无条件优先搬入老年代,忽略老年代剩余容量",
"C": "晋升只发生在 Full GC 时,Minor GC 从不搬移对象到老年代",
"D": "对象年龄等于 1 且 Eden 有足够空间时也必然留在新生代"
},
"answer": "A",
"explanation": "A 对:HotSpot 为每个存活对象记录年龄(age),每安全通过一次 Minor GC 并在幸存区间移动年龄 +1;当到达阈值(默认 MaxTenuringThreshold=15)时晋升到老年代。B 错:晋升并非无条件——除了年龄,还有动态年龄判定与“提前晋升(to-space 存量 / 空间不足时的晋升)”策略,会参考老年代剩余空间与 Survivor 容量,不能一概『无条件优先晋升』。C 错:晋升主要发生在 Minor GC(新生代回收)过程中,Full/Major GC 也会做空间整理,但『只发生在 Full GC』不对。D 错:年龄达标时可晋升,且存在动态晋升(动态年龄)等情况,并非年龄 1 就一定留在新生代;此判断限制性太强,可作排除。",
"source": null,
"related": []
},
{
"id": "sc-007",
"type": "single_choice",
"difficulty": 3,
"tags": [
"g1"
],
"question": "关于 G1 回收器的 Region 结构与动态角色,下列说法正确的是:",
"options": {
"A": "Region 是固定分区,其角色(Eden/Survivor/Humongous/Old/free)从头到尾不可改变",
"B": "大对象(Humongous,超过 Region 一半)会独占多个连续的 free Region,不需要复制搬移",
"C": "G1 的 Region 大小在运行时会根据对象存活率动态伸缩调整",
"D": "类推 CMS,G1 也允许对象在普通 Eden 之外直接分配在老年代一大块连续空间"
},
"answer": "B",
"explanation": "B 对:当单个对象因为数组等太大、超过 Region 容量的一半(Half Region)时,G1 会为它分配连续多个 Region 作为 Humongous(H)区域,这类大对象通常不做搬移整理,直接分配,这是 G1 处理大对象的专门机制。A 错:Region 的角色(Eden/Survivor/Humongous/Old/free)在运行时是「动态」的,会随着分配与 GC 变化,一个 Region 回收清空后可被重新用作其他角色,并非从创建到销毁都一成不变。C 错:Region 大小在 JVM 启动时就由配置/堆大小确定下来,运行期间是固定不变的,并不会根据堆使用率动态调整——这与 G1 动态调整的只是 Region 的「角色」而不改变其「大小」的事实相反,故 C 是错误项。D 错:G1 中对象一律分配到大小统一的 Region(含 Humongous 连续区),没有『类似 CMS 直接在老年代分一大块』的分配方式,前后矛盾。综合来看,唯一完全成立的正确说法是 B。",
"source": null,
"related": []
},
{
"id": "sc-008",
"type": "single_choice",
"difficulty": 2,
"tags": [
"gc-root"
],
"question": "下列选项中,不属于 JVM「GC Root」的是:",
"options": {
"A": "虚拟机栈(栈帧)中的局部变量",
"B": "静态变量(类静态字段)引用的对象",
"C": "常量池中引用指向的对象",
"D": "被对象内普通私有字段引用的对象"
},
"answer": "D",
"explanation": "A、B、C 均属于典型的 GC Roots 类别:虚拟机栈中局部变量、静态变量持引用、常量池(运行时常量池中的字面量引用)等都能作为可达性分析的起点。D 正确(即不是 GC Root 的一项):被普通对象内的私有字段引用的对象本身不是起点,它只有通过根(Root)经由引用链才能被判定可达;判定依据是从 Roots 出发能否达到,而不是字段是否可达就是指根。其余例如 JNI 引用、活跃线程、synchronized 锁关联对象、Class 对象也是 Roots——题目共七种,这里给出七个选项所列举的根中,D 恰恰不属于。",
"source": null,
"related": []
},
{
"id": "sc-009",
"type": "single_choice",
"difficulty": 4,
"tags": [
"three-color-marking"
],
"question": "关于三色标记法在并发标记中的「漏标」与「多标」,下列说法正确的是:",
"options": {
"A": "并发标记中出现漏标(把仍可达的对象回收,即误回收活对象)最严重,通常需要「增量更新」或「SATB 屏障」等读/写屏障防止",
"B": "标记时对象由白变黑后,只要应用线程后续不再被对象引用即安全,不会发生任何误回收",
"C": "多标只是多回收相当数量本应保留的内存,没有任何副作用",
"D": "三色标记必须先全部串行 STW 完成,不能结合并发 GC 一起使用"
},
"answer": "A",
"explanation": "A 对:并发标记流程应用线程也在写引用,可能把已标记为黑的对象再赋值给白对象、或让灰对象失去指向→导致可达对象最终仍保持白色被误回收(漏标)。为防漏标,需要写屏障(增量更新/Incremental Update)或 SATB,如 CMS、G1/ZGC 的对应方案。B 错:并发写(尤其是「黑对象引用新的白」)恰恰是三色算法需要特别处理的场景,并非跳过就好。C 错:多标(浮动垃圾)虽不是致命错误(对象本应被回收却多留一个周期),但会延长占用、增加内存与集合成本,不能说『没有任何正确性问题』。D 错:三色标记正是为了并发标记而设,可结合增量更新 / SATB 在并发标记阶段工作,并非必须先全量 STW 才能完成标记。",
"source": null,
"related": []
},
{
"id": "sc-010",
"type": "single_choice",
"difficulty": 5,
"tags": [
"cms",
"g1"
],
"question": "在并发收集器 CMS 与 G1 的整体设计辨析中,下列说法正确的是:",
"options": {
"A": "CMS 能在不搬移对象的前提下并发清除,其代价是堆内碎片化,长时间运行后可能因内存碎片触发一次碎片整理式的 Full GC",
"B": "G1 与 CMS 支持几乎一样的按代收集方式,二者没有根本差异",
"C": "CMS 标注中的写屏障使用 SATB,而 G1 使用增量更新跟踪漏标",
"D": "G1 的 Region 并不需要对大对象支持,Humongous 对象可直接放在 Eden 老化"
},
"answer": "A",
"explanation": "A 对:CMS(Concurrent Mark Sweep)是「标记-清除」型并发收集器,清除阶段不搬移对象、与应用并发执行,这带来了堆内日益碎片化的副作用;当碎片积累到导致对象分配失败时,会触发一次对堆做整理的 Serial Old 式 Full GC,这正是长时间运行 CMS 后停顿骤升的常见原因。B 错:G1 是分区(Region)+ 可预测停顿的混合式收集器,支持『混合回收(Mixed GC)』同时收集新生代与部分老年代 Region,与 CMS 的「标记-清除并发 + 一次性 Full 整理」机制差别明显。C 错:正好反了——CMS 的并发标记用「增量更新(Incremental Update)」,而 G1 用「SATB(Snapshot-At-The-Beginning)」在标记起点打快照来跟踪引用变化;把两者对调是错误的。D 错:G1 对大对象的支持需要 Humongous(连续多 Region)机制,Humongous 对象并不放入普通 Eden 老化,而是占用一个或多个 H 连续区域,故 D 错。综上,唯一正确的是 A。",
"source": null,
"related": []
}
]
}