diff --git a/topics/gc-special/gc-deep-dive/fill_blank.json b/topics/gc-special/gc-deep-dive/fill_blank.json new file mode 100644 index 0000000..59e5dc6 --- /dev/null +++ b/topics/gc-special/gc-deep-dive/fill_blank.json @@ -0,0 +1,128 @@ +{ + "topic": "gc-deep-dive", + "type": "fill_blank", + "schema_version": "1.0.0", + "generated": "2026-09-03T00:00:00Z", + "questions": [ + { + "id": "fb-001", + "type": "fill_blank", + "difficulty": 1, + "tags": ["gc", "memory", "jvm", "three-color-marking", "collections"], + "question": "Python 默认采用的 GC 机制是___计数,而 Java 与 Go 采用的则是从 GC Roots 出发的___分析;Rust 则在编译期通过___所有权机制来管理内存。", + "answer": ["引用计数", "可达性", "所有权"], + "answer_rule": "all", + "explanation": "Python 使用引用计数(配合周期检测器处理循环引用);Java/Go 使用追踪式可达性分析;Rust 依赖编译期的所有权(ownership)和借用规则,无需运行时 GC。", + "source": null, + "related": [] + }, + { + "id": "fb-002", + "type": "fill_blank", + "difficulty": 1, + "tags": ["gc", "jvm", "memory", "collections"], + "question": "Swift 采用的自动引用计数简称__,它属于___计数类回收机制而非追踪式 GC。", + "answer": ["ARC", "引用"], + "answer_rule": "all", + "explanation": "Swift 的 ARC(Automatic Reference Counting)在编译期插入 retain/release,是一种引用计数式方案,与 Java/Go 的追踪式 GC 不同。", + "source": null, + "related": [] + }, + { + "id": "fb-003", + "type": "fill_blank", + "difficulty": 2, + "tags": ["gc", "jvm", "memory", "collections"], + "question": "JVM 进行垃圾回收时所有业务线程必须暂停,这一现象缩写作 ___;STW 之外,GC 带来的主要副产品还包括额外的内存开销,通常约为堆的 ___%。", + "answer": ["STW", "5%到20%"], + "answer_rule": "all", + "explanation": "STW = Stop-The-World,指 GC 期间暂停用户线程;追踪式 GC 通常额外占用 5%~20% 的堆内存(如对象头标记、卡片表、缓冲等)。", + "source": null, + "related": [] + }, + { + "id": "fb-004", + "type": "fill_blank", + "difficulty": 2, + "tags": ["gc", "memory", "collections"], + "question": "针对内存碎片治理,标记-清除完成后可继续压缩以提高熵密度;另一种思路是___算法把存活对象复制到另一块连续区域;G1 等现代回收器则采用按 ___ 划分 Region 的自适应方式。", + "answer": ["复制", "区域"], + "answer_rule": "all", + "explanation": "复制(copying)算法从一块空间复制存活对象到另一块以消除碎片;分代/区域式采用自适应的复制与晋升策略管理碎片。", + "source": null, + "related": [] + }, + { + "id": "fb-005", + "type": "fill_blank", + "difficulty": 2, + "tags": ["gc", "jvm", "memory", "collections"], + "question": "经典 HotSpot 新生代内存被划分为三个区,默认占比为 Eden 占 80%、存活区 S0 与 S1 各占 ___%;对象经历一次 Minor GC 后年龄加 1,达到晋升阈值默认值 ___ 时被提升至老年代。", + "answer": ["10%", "15"], + "answer_rule": "all", + "explanation": "默认 Eden(80%)/S0(10%)/S1(10%),晋升阈值 -XX:MaxTenuringThreshold 默认 15,对象年龄达到即晋升到老年代。", + "source": null, + "related": [] + }, + { + "id": "fb-006", + "type": "fill_blank", + "difficulty": 3, + "tags": ["gc", "jvm", "memory", "collections"], + "question": "CMS 垃圾回收器(旧)采用标记-清除,大致经历四个阶段;而 G1 不再沿用固定分区,而是把堆划分为等大小的 ___ 来承载新生代/老年代;ZGC 则使用 ___ 来快速记录对象状态以支撑极低的暂停。", + "answer": ["Region", "染色指针"], + "answer_rule": "all", + "explanation": "G1(Garbage First)把堆抽象地划分成若干等大小 Region;ZGC 采用了染色指针(colored pointers)将部分状态编码在地址指针上,实现极低延迟。", + "source": null, + "related": [] + }, + { + "id": "fb-007", + "type": "fill_blank", + "difficulty": 4, + "tags": ["gc", "jvm", "memory", "three-color-marking", "collections"], + "question": "三色标记法中:灰色表示对象已被扫描但其若干子对象还未遍历完,白色表示___候选,黑色表示已全部处理完成;若某存活对象在所有引用恢复之后仍被标成白色,则发生了漏标,需满足两个条件。", + "answer": ["待回收", "漏标"], + "answer_rule": "all", + "explanation": "三色标记:白=未访问(潜在垃圾候选)、灰=已访问但子对象未完、黑=子对象已全处理。并发标记时需要满足条件之一(增量更新或 SATB)来避免漏标。", + "source": null, + "related": [] + }, + { + "id": "fb-008", + "type": "fill_blank", + "difficulty": 4, + "tags": ["gc", "jvm", "memory", "collections"], + "question": "在增量更新的三色标记中,防止存活对象被误回收(漏标)需要同时满足两个条件:在垃圾收集器标记期间,某对象字段上的新指针必须记录到当前视图,并且该对象本身必须属于___(已被标记)集合。G1 默认采用基于栈的 ___ 快照来记录这些变化。", + "answer": ["灰色", "SATB"], + "answer_rule": "all", + "explanation": "漏标的两条件:①对某字段写入一个新引用(引用替换);②在抛弃的旧引用被记录前回收前,该对象已被标记过。G1 采用 SATB(增量式)保存开始时快照并记录变化,保证不漏标。", + "source": null, + "related": [] + }, + { + "id": "fb-009", + "type": "fill_blank", + "difficulty": 5, + "tags": ["gc", "jvm", "memory", "three-color-marking", "collections"], + "question": "三色标记中的“漏标”指存活对象被错误当作白色从而被回收冻结的 bug。避免漏标需要两个条件同时成立:①某白色对象被(已被标记的)黑色对象引用(即写入了新引用);②在该黑色对象的新引用被记录(额外扫描机会)之前,其旧引用已被收集器丢弃。若破坏其中任一条件即可避免漏标,CMS 使用___更新,G1 采用“起始于快照”(___)策略。", + "answer": ["增量", "SATB"], + "answer_rule": "all", + "explanation": "防止漏标的两策略:增量更新(CMS,把被修改对象的灰色标记重新传播)与 SATB(Snapshot-at-the-Beginning,G1,记录并发开始的引用快照)。", + "source": null, + "related": [] + }, + { + "id": "fb-010", + "type": "fill_blank", + "difficulty": 5, + "tags": ["gc", "jvm", "memory", "three-color-marking", "collections"], + "question": "进行可达性分析的垃圾回收时,标记阶段从若干被外部持有的根(Roots)出发,主要根类型包括:Java 虚拟机栈中帧局部的局部变量/参数、___中的静态对象、JNI 本地/全局引用、运行时常量池中的对象等。", + "answer": ["方法区或堆中静态引用域"], + "answer_rule": "all", + "explanation": "GC Roots 的典型成员:栈帧局部变量表、方法区静态字段/引用、JNI 引用、运行时常量池里的字符串等。此处提供“方法区/堆静态引用”类答案。", + "source": null, + "related": [] + } + ] +} \ No newline at end of file diff --git a/topics/gc-special/gc-deep-dive/meta.json b/topics/gc-special/gc-deep-dive/meta.json new file mode 100644 index 0000000..133aa14 --- /dev/null +++ b/topics/gc-special/gc-deep-dive/meta.json @@ -0,0 +1,67 @@ +{ + "slug": "gc-deep-dive", + "name": "GC 综合深入", + "description": "垃圾回收专题:GC 概念、内存碎片与算法、分代 GC、回收器对比、GC Roots、三色标记法", + "tags": [ + "CMS", + "G1", + "GC", + "GC Roots", + "GC代价", + "RSet", + "Region", + "SATB", + "StopTheWorld", + "ZGC", + "三色标记", + "亚毫秒", + "停顿", + "内存碎片", + "分代收集", + "分配", + "可达性", + "可达性分析", + "可预测停顿", + "基础概念", + "复制算法", + "年轻代", + "并发标记", + "引用计数", + "循环引用", + "性能", + "晋升", + "染色指针", + "标记压缩", + "标记清除", + "泄漏", + "漏标", + "碎片化", + "算法对比", + "老年代", + "解决方式", + "读屏障" + ], + "difficulty_range": [ + 2, + 4 + ], + "schema_version": "1.0.0", + "updated": "2026-09-03", + "question_files": [ + "single_choice", + "fill_blank", + "true_false", + "multiple_choice", + "short_answer" + ], + "stats": { + "total": 50, + "by_type": { + "single_choice": 10, + "fill_blank": 10, + "true_false": 10, + "multiple_choice": 10, + "short_answer": 10 + } + } +} diff --git a/topics/gc-special/gc-deep-dive/multiple_choice.json b/topics/gc-special/gc-deep-dive/multiple_choice.json new file mode 100644 index 0000000..70acbc1 --- /dev/null +++ b/topics/gc-special/gc-deep-dive/multiple_choice.json @@ -0,0 +1,278 @@ +{ + "topic": "gc-deep-dive", + "type": "multiple_choice", + "schema_version": "1.0.0", + "generated": "2026-09-03T00:00:00Z", + "questions": [ + { + "id": "mc-001", + "type": "multiple_choice", + "difficulty": 3, + "tags": [ + "GC", + "GC Roots", + "可达性分析" + ], + "question": "以下是 HotSpot JVM 进行可达性分析时使用的 GC Roots 类型的有哪些?(多选)", + "options": { + "A": "静态变量(静态字段引用的对象)", + "B": "局部变量/栈帧中引用的对象", + "C": "活跃线程(当前正在运行的线程对象)", + "D": "class 对象(类的元数据对象)", + "E": "方法参数临时值(调用时压栈的立即数完成态对象)" + }, + "answer": [ + "A", + "B", + "C", + "D" + ], + "explanation": "GC Roots 的七种典型来源包括:①静态变量(静态字段引用的对象);②局部变量/栈引用(栈帧局部变量表中引用的对象);③JNI 引用(Native 方法引用的对象);④常量池/常量引用;⑤活跃线程对象;⑥类对象(class 对象);⑦synchronized 锁对象。因此 A、B、C、D 都对。E 是干扰项:方法参数临时变量同样属于栈帧局部变量范围,已经由 B 覆盖,并不存在单独列出的\"立即对象\"这一 Root 类型,且描述本身不严谨。", + "source": null, + "related": [] + }, + { + "id": "mc-002", + "type": "multiple_choice", + "difficulty": 3, + "tags": [ + "GC", + "STW", + "GC代价", + "吞吐量" + ], + "question": "以下哪些是垃圾回收(GC)本身带来的代价?(多选)", + "options": { + "A": "Stop-The-World(STW)暂停导致应用线程停顿", + "B": "GC 需要额外维护引用/标记信息占用内存", + "C": "垃圾回收必然提高应用的吞吐量", + "D": "产生内存碎片,可能导致大对象分配失败", + "E": "GC 停顿时间不可预测,影响延迟稳定性" + }, + "answer": [ + "A", + "B", + "D", + "E" + ], + "explanation": "GC 的主要代价包括:STW 暂停(A)、额外内存开销用于标记信息/卡表等(B)、内存碎片增加分配失败概率(D)、停顿时间不可预测影响延迟(E),以及 CPU 消耗。C 是明确的干扰项:GC 会消耗 CPU、占用内存,通常会降低吞吐量或与业务争抢资源,绝不可能\"必然提高吞吐量\"。", + "source": null, + "related": [] + }, + { + "id": "mc-003", + "type": "multiple_choice", + "difficulty": 4, + "tags": [ + "GC", + "CMS", + "并发标记" + ], + "question": "关于 CMS 收集器致命缺陷的说法,正确的有哪些?(多选)", + "options": { + "A": "会产生\"浮动垃圾\"(Floating Garbage),需要预留空间", + "B": "使用标记-清除算法,容易产生内存碎片", + "C": "当并发清除失败时进入 Concurrent Mode Failure,退化为 Serial Old 阻塞回收", + "D": "对 CPU 资源不敏感,单核环境完全不受影响", + "E": "CMS 采用增量更新(Incremental Update)来保证标记正确性" + }, + "answer": [ + "A", + "B", + "C", + "E" + ], + "explanation": "CMS 的四大致命问题:①浮动垃圾——并发阶段新增垃圾需要预留空间(A);②标记-清除算法天然产生碎片(B);③出现 Concurrent Mode Failure 时退化为 Serial Old 全量回收、STW 暂停长(C);④CMS 对 CPU 资源敏感——并发回收占用于多个线程,单核环境下基本退化。E 也正确:CMS 的并发标记/清理正是依靠增量更新(Incremental Update)保证标记正确性。D 说法与事实相反,是干扰项。", + "source": null, + "related": [] + }, + { + "id": "mc-004", + "type": "multiple_choice", + "difficulty": 3, + "tags": [ + "GC", + "G1", + "Region" + ], + "question": "G1 收集器中,Region 可能充当以下哪些角色?(多选)", + "options": { + "A": "Eden(新生代对象分配区)", + "B": "Survivor(存活对象复制区)", + "C": "Old(老年代对象区)", + "D": "Humongous(大对象专用区)", + "E": "Perm(永久代区)" + }, + "answer": [ + "A", + "B", + "C", + "D" + ], + "explanation": "G1 将内存划分为大小相等的 Region,每个 Region 可以动态扮演 Eden、Survivor、Old、Humongous 或 free 五种角色之一,因此 A、B、C、D 均正确。E 是干扰项:Perm(永久代)是 JRockit / HotSpot 的类元数据概念,G1 Region 中并没有 Perm 角色(JDK8 起的元空间已移出堆外)。", + "source": null, + "related": [] + }, + { + "id": "mc-005", + "type": "multiple_choice", + "difficulty": 5, + "tags": [ + "GC", + "并发标记", + "漏标", + "SATB" + ], + "question": "并发标记解决\"漏标\"(对象被错误判定为不可达)问题的典型方案有哪些?(多选)", + "options": { + "A": "增量更新(Incremental Update)", + "B": "SATB(Snapshot At The Beginning,起始快照)", + "C": "染色指针(Colored Pointer)+ 读屏障(Read Barrier)", + "D": "保守式扫描直接标记所有存活对象", + "E": "使用弱引用清理避免对象被漏标" + }, + "answer": [ + "A", + "B", + "C" + ], + "explanation": "并发标记过程中黑色对象直接引用白色对象会造成漏标,主流方案有三:①增量更新(CMS 采用)——记录被重写的黑色对象引用(A);②SATB(G1 采用)——在起始快照下记录被删除的引用(B);③染色指针+读屏障(ZGC/CMS 新旧版采用)——利用指针染色标记引用状态(C)。D 是干扰项,\"保守式\"并非解决漏标的方案;E 与漏标问题无关,垃圾引用机制与标记的正确性回路没有直接对应。", + "source": null, + "related": [] + }, + { + "id": "mc-006", + "type": "multiple_choice", + "difficulty": 4, + "tags": [ + "GC", + "老年代", + "标记-压缩", + "CMS" + ], + "question": "HotSpot 在老年代(Major GC)回收时可采用的算法组合的有哪些?(多选)", + "options": { + "A": "标记-清除(Mark-Sweep)作为 CMS 的核心算法", + "B": "标记-压缩(Mark-Compact)用于整理旧对象减少碎片", + "C": "标记-复制(Copying)在老年代且回收率低时作为首选", + "D": "CMS 采用标记-清除,遇碎片过多时退回标记-压缩的 Serial Old", + "E": "三色标记法本身即可作为独立的老年代回收算法" + }, + "answer": [ + "A", + "B", + "D" + ], + "explanation": "老年代常见的回收算法组合:CMS 使用标记-清除(A),Serial Old / 回退的收集器使用标记-压缩(B),当 CMS 碎片过多/并发失败时启动 Serial Old 进行标记-压缩整理(D),这些都正确。C 是干扰项:复制算法只用于存活率低的新生代,老年代存活率高,不会以复制算法为首选。E 错误:三色标记只是标记阶段的概念,不是独立可以完成回收的完整算法。", + "source": null, + "related": [] + }, + { + "id": "mc-007", + "type": "multiple_choice", + "difficulty": 4, + "tags": [ + "GC", + "收集器", + "低延迟" + ], + "question": "从设计目标上看,以下哪些垃圾收集器在核心设计中就明确以\"极低延迟\"(近实时、亚毫秒级停顿)为优先?(多选)", + "options": { + "A": "ZGC(Z Garbage Collector)", + "B": "Shenandoah", + "C": "Serial", + "D": "CMS(Concurrent Mark Sweep)", + "E": "G1(Garbage-First)" + }, + "answer": [ + "A", + "B" + ], + "explanation": "ZGC(A)和 Shenandoah(B)是近年来专门为\"极低延迟\"设计的收集器:ZGC 基于染色指针 + 读屏障,转换到并发转移阶段,停顿可以控制在亚毫秒级;Shenandoah 同样通过并发转移(转移过程中读写屏障)把停顿压低到接近 ZGC 的水平。C、D、E 是干扰项:Serial 是单线程短停顿无法保证的收集器;CMS 虽然以\"低停顿\"为卖点,但它是用牺牲吞吐换取停顿下降,且存在浮动垃圾/并发失败,并非以\"极低延迟\"为核心设计目标;G1 只是提供了可配置的停顿时间目标(默认把 STW 控制在几百 ms 内),并不以亚毫秒级停顿为设计初衷。", + "source": null, + "related": [] + }, + { + "id": "mc-008", + "type": "multiple_choice", + "difficulty": 3, + "tags": [ + "GC", + "Minor GC", + "新生代" + ], + "question": "关于 Minor GC 触发条件与回收范围的描述,正确的有哪些?(多选)", + "options": { + "A": "主要在 Eden 区满时被触发", + "B": "回收范围只涉及新生代(Eden + Survivor)", + "C": "Minor GC 做完后会把存活对象晋升(Promotion)到老年代", + "D": "Minor GC 必然每次都做完整堆回收", + "E": "Minor GC 通常伴随全程 Stop-The-World,停顿短" + }, + "answer": [ + "A", + "B", + "C", + "E" + ], + "explanation": "Minor GC(也被称为 Young GC):A 启动——当 Eden 区满时触发;B 回收范围通常只在新生代(Eden 和 Survivor);C 经过几次复制后仍存活的对象会通过晋升(Promotion)进入老年代;E 使用复制算法且只处理新生代,相对停顿短。D 是干扰项:Minor GC 并不会做完整堆回收,它是局部回收,与 Full GC(全堆回收)是不同概念。", + "source": null, + "related": [] + }, + { + "id": "mc-009", + "type": "multiple_choice", + "difficulty": 2, + "tags": [ + "GC", + "GC Roots", + "JNI", + "常量池" + ], + "question": "以下哪些属于 GC Roots 的典型来源?(多选)", + "options": { + "A": "静态变量(静态字段引用的对象)", + "B": "恒定常量池(常量/字面量引用的对象)", + "C": "JNI 引用(本地方法引用的对象)", + "D": "synchronized 锁对象(被锁住的对象)", + "E": "数据库连接池里空闲的对象" + }, + "answer": [ + "A", + "B", + "C", + "D" + ], + "explanation": "GC Root 的常见源头:静态变量(A)、常量池/常量引用(B)、JNI 引用(C)、synchronized 锁对象(D),以及活跃线程、局部变量、类对象等,都正确。E 是干扰项:连接池缓存对象尽管可能存活,但若没有 A-D 等任一 Root 直接/间接引用,仍会被判定为不可达并回收——\"被缓存\"这一事实本身并不是独立的 GC Root 类型。", + "source": null, + "related": [] + }, + { + "id": "mc-010", + "type": "multiple_choice", + "difficulty": 5, + "tags": [ + "GC", + "G1", + "Region", + "Humongous" + ], + "question": "关于 G1 对大对象和 Region 的说法,哪些是正确的?(多选)", + "options": { + "A": "超过 Region 大小一半的大对象分配专有的 Humongous Region", + "B": "Humongous 对象不做局部转移,回收时需要提前整体判定", + "C": "Region 只有在被分配为 Eden 后才会变成 Old", + "D": "大对象分配可能触发 Young GC(新生代 GC)以释放空间", + "E": "大区碎片化是大对象分配失败的直接原因" + }, + "answer": [ + "A", + "B", + "D" + ], + "explanation": "A 正确:超过 Region 大小一半的对象被标记为 Humongous,放入连续的 Humongous Region。B 正确:巨型对象不参与普通 Region 的复制/转移,需要专门整体处理(Humongous 分配/清除依赖整块判定)。D 正确:分配大对象时若没有足够连续 Region,可能会触发 Young GC 来腾出空间。C 是伪项:Region 角色并非只能 Eden→Old,Eden 和 Survivor 之间互相转换,Old Region 也可能通过分配直接转入。E 错误:碎片影响大对象分配,但并非\"分配失败\"的直接原因,概念表述混乱。", + "source": null, + "related": [] + } + ] +} \ No newline at end of file diff --git a/topics/gc-special/gc-deep-dive/short_answer.json b/topics/gc-special/gc-deep-dive/short_answer.json new file mode 100644 index 0000000..c799bd5 --- /dev/null +++ b/topics/gc-special/gc-deep-dive/short_answer.json @@ -0,0 +1,278 @@ +{ + "topic": "gc-deep-dive", + "type": "short_answer", + "schema_version": "1.0.0", + "generated": "2026-09-03T00:00:00Z", + "questions": [ + { + "id": "sa-001", + "type": "short_answer", + "difficulty": 2, + "tags": [ + "GC", + "引用计数", + "可达性分析", + "基础概念" + ], + "question": "简述垃圾回收(Garbage Collection)是什么,其中的对象会被什么方式判定为垃圾?请对比两种主要判定方式——引用计数 与 可达性分析——的差异、优缺点各列出至少 2 条。", + "answer": "垃圾回收是一种自动内存管理机制,用于自动识别并回收那些不再被程序引用的对象所占用的内存,避免手动 free 造成的悬垂指针和内存泄漏。判定垃圾的两种主流方式如下。\n\n一、引用计数(Reference Counting)\n- 原理:为每个对象维护一个引用计数器,每当有一个新引用指向它时计数 +1,引用被移除时计数 -1;当计数归 0 时对象被立即回收。\n- 优点:(1) 回收及时,对象成为垃圾的当下即可被回收,无停顿感;(2) 实现简单,增量式完成,不需要全局扫描,适合实时性要求高的场景。\n- 缺点:(1) 无法解决循环引用——A 引用 B、B 引用 A 时两者计数永不为 0,导致垃圾无法回收造成泄漏;(2) 每次赋值操作都要同步修改计数器,带来额外性能开销;(3) 计数更新不是原子的,多线程下需要同步。\n\n二、可达性分析(Trace / Reachability Analysis,如从 GC Roots 遍历)\n1. 原理:从一组被称为 GC Roots 的根对象出发,沿引用链向下遍历,凡是能到达的对象为存活对象,其余不可达的对象即为垃圾,交给收集器回收。\n2. 优点:(1) 能正确解决循环引用,循环引用的对象只要不被根可达,整体仍会被回收;(2) 不需要对每次赋值操作计数,开销集中在 GC 阶段。\n3. 缺点:(1) 需要 Stop-The-World 扫描,停顿相对较长(可通过分代、并发优化缓解);(2) 实现复杂度较高。\n\n差异总结:引用计数看重对象被引用的次数,即时、增量;可达性分析看重对象是否从根可达,周期性、批量。现代主流 JVM(HotSpot)采用可达性分析。", + "keywords": [ + "垃圾回收", + "引用计数", + "可达性分析", + "循环引用", + "GC Roots", + "引用计数优点", + "可达性分析优点", + "内存回收" + ], + "scoring_rubric": "解释垃圾回收概念 1 分;引用计数原理与至少 2 条优缺点 3 分;可达性分析原理与至少 2 条优缺点 3 分;明确指出循环引用是二者关键差异 1~2 分;比较与总结 1 分。总分 10 分。", + "explanation": "本题目的是打基础,考察对 GC 判定方式本质的把握。引用计数与可达性分析的最大分野在于对循环引用的处理,回答时两者优缺点都能举出 2 条以上、并触及循环引用,即可拿高分。", + "source": null, + "related": [] + }, + { + "id": "sa-002", + "type": "short_answer", + "difficulty": 3, + "tags": [ + "分代收集", + "年轻代", + "老年代", + "晋升" + ], + "question": "为什么要对堆进行分代(Generational)收集?请简述年轻代/老年代的结构,以及对象从年轻代晋升(Promotion)到老年代的过程,说明分代收集的理论依据。", + "answer": "1. 分代的理论依据是弱分代假设(Weak Generational Hypothesis):绝大多数对象存活时间极短,很快成为垃圾;只有少部分对象会长期存活。针对不同存活特质的对象采用不同回收策略,能在保持回收效果的同时大幅降低扫描与复制成本。\n\n2. 年轻代(Young Generation)\n1. 通常包含 Eden 区与两个 Survivor 区(S0、S1,等大小)。新对象分配在 Eden。\n2. 特点:回收时存活率低、垃圾占比高,适合采用复制算法——把存活对象复制到 Survivor,一次性清空 Eden 与被清理的 Survivor,无碎片。\n3. Young GC / Minor GC:仅扫描年轻代,速度快。\n\n3. 老年代(Old Generation)\n- 存放存活时间长、晋升上来的对象。存活率高,不适合反复复制,常用标记清理或标记压缩。\n- 老年代空间不足或分配担保失败时触发 Major / Full GC。\n\n4. 晋升过程(对象从年轻人进入老年的完整链路)\n1. 存活对象在 Minor GC 中被复制到 Survivor,每经过一次 Minor GC 且存活,其年龄(age)就 +1。\n2. 当年龄到达阈值(HotSpot 默认 15,可用 -XX:MaxTenuringThreshold 调整),在后续 GC 时晋升入老年代。\n3. 大对象(超过 -XX:PretenureSizeThreshold)直接在老年代分配,避免反复复制。\n4. 若 Survivor 中存活对象过多导致空间不足,超过容量的部分会提前动态晋升到老年代。\n\n5. 总结:年轻代 GC 频繁但对空间、耗时短;老年代 GC 频率低但耗时长,兼顾吞吐与停顿的平衡。", + "keywords": [ + "分代", + "弱分代假设", + "Eden", + "Survivor", + "晋升", + "年龄 15", + "Promotion", + "复制算法" + ], + "scoring_rubric": "答出理论依据(弱分代假设/大部分对象短命)2 分;年轻代结构 Eden+Survivor 及回收方式 3 分;对象晋升过程(年龄+1、到 15、Survivor 满提前晋升、大对象直接入老年代)逐项给分 4 分;总结 1 分。总分 10 分。", + "explanation": "分代是 HotSpot 的核心思路。应抓住为什么分代(短命对象多)与怎么分(年轻代复制、老年代标记压缩)以及晋升的完整链路。", + "source": null, + "related": [] + }, + { + "id": "sa-003", + "type": "short_answer", + "difficulty": 4, + "tags": [ + "CMS", + "G1", + "并发标记", + "停顿" + ], + "question": "对比 CMS(Concurrent Mark Sweep)与 G1(Garbage First):为什么 G1 取代了 CMS 成为默认收集器?请具体列出 CMS 至少在 3 个方面的缺陷,并说明 G1 是如何针对性地进行改进的。", + "answer": "CMS 以最小化停顿为目标,大部分标记与清扫阶段与用户线程并发执行;但并发阶段与用户线程争抢 CPU、且会产生大量内存碎片,最终被 G1 取代。G1 从 JDK 9 起成为默认收集器。\n\n一、CMS 的主要缺陷\n1. 内存碎片:采用标记-清除,产生大量内存碎片,老年代空间不连续,导致需要频繁以 Full GC 作为补偿(Full GC 需要 STW,停顿极长)。\n2. 浮动垃圾(Floating Garbage):并发阶段新产生的垃圾无法在本轮回收,只能推迟到下次 GC,可能引发提前的 Full GC。\n3. CPU 争抢:并发标记/清理阶段与用户线程争抢 CPU,在 CPU 资源紧张的机器上吞吐和性能下降明显。\n4. 复杂度高、临界失败代价大:过程分为初始标记、并发标记、重新标记、并发清理、重置五阶段,出现 concurrent mode failure 时回退为串行的 Full GC。\n5. 仅针对老年代:年轻代需搭配 ParNew 等收集器,二者需协同。\n\n 二、G1 的针对性改进\n1. Region 化:不再物理划分年轻代/老年代,把堆划分为大量相同大小的 Region,每次只回收价值最大的若干 Region(故称 Garbage First),以可预测停顿模型替代 CMS 的模糊停顿。\n2. 复制+压缩:对目标 Region 使用复制/压缩而非标记清除,回收后空间连续,大幅降低内存碎片,减少 Full GC 概率。\n3. RSet(Remembered Set):按 Region 记录跨区引用,只回收部分 Region 时不必扫描整个堆,减少 GC 工作量。\n4. 并发标记 + 可预测停顿:同样采用并发标记(三色标记)实现低停顿,并通过可预测停顿模型控制停顿时间。\n\n结论:G1 以 Region 化 + 压缩式回收 + RSet 定位 + 可预测停顿设计,从根上解决了 CMS 的碎片、浮动垃圾与不清楚的停顿问题。", + "keywords": [ + "CMS缺陷", + "G1", + "Region", + "内存碎片", + "Full GC", + "浮动垃圾", + "可预测停顿", + "并发标记" + ], + "scoring_rubric": "答出 CMS 至少 3 个确定性缺陷(碎片、CPU 争抢、浮动垃圾、临界失败)各 1 分共 3~5 分;答出 G1 的对应改进(Region、复制/压紧、RSet、可预测停顿)2~3 个 4 分;能说明取消替代关系 1 分。总分 10 分。", + "explanation": "本题考查对 CMS 方案与 G1 设计逻辑的理解。核心是 CMS 的『标记-清除』天然带来碎片与浮动垃圾,G1 通过 Region + 压缩 + RSet 彻底革新了处理体系。", + "source": null, + "related": [] + }, + { + "id": "sa-004", + "type": "short_answer", + "difficulty": 3, + "tags": [ + "GC代价", + "StopTheWorld", + "性能" + ], + "question": "请列举垃圾回收带来的代价,尽量详细(最好 7 条左右),每条用一句或一两句说明其影响。", + "answer": "垃圾回收虽是现代语言的自动内存管理手段,但也会带来一系列不可忽视的代价:\n\n1. Stop-The-World(STW)停顿:GC 过程中需要暂停应用线程,导致服务卡顿、延迟升高——这是最核心的代价。\n2. CPU 开销/吞吐量损失:GC 本身要消耗 CPU(标记、复制、压缩、并发线程等),与业务争抢 CPU,降低整体吞吐量。\n3. 内存开销/占用率:GC 需要额外的堆内存管理(如复制算法要预留等大 Survivor 空间、Region 划分、元数据如对象头、RSet、标记位图),使有效可用内存减少。\n4. 内存碎片化:尤其标记-清除导致空闲内存不连续,无法分配大对象,后续只能 Full GC 或反复整理。\n5. 浮动垃圾堆积:并发/增量式 GC 无法收集标记后新产生的垃圾,导致内存利用率下降、回收延迟。\n6. 吞吐与延迟的二择:专注于低延迟的收集器(CMS/G1/ZGC)往往牺牲吞吐与额外 CPU,懒人反而牺牲延迟,二者难以兼得。\n7. 延迟不稳定性与抖动(Lag Spike):GC 发生时机不可预测,导致业务延迟随机抖动,影响响应稳定性。\n\n(附加)8. 调优与运维成本:GC 日志分析、参数调优、故障排查均带来额外复杂度。", + "keywords": [ + "Stop-The-World", + "CPU开销", + "内存占用", + "碎片化", + "浮动垃圾", + "吞吐量", + "延迟抖动", + "调优成本" + ], + "scoring_rubric": "每答对 1 个清晰合理的代价记 1 分,能联系 STW/碎片/浮动/资源与资源竞争等核心概念;列出 7 条即满分 8 分以上,弹性给分。逻辑清晰、触及本质者可给加分。", + "explanation": "GC 的七大代价是底层思考常见考点。核心围绕 STW、CPU/内存资源成本、碎片、浮动垃圾、吞吐与延迟的权衡展开,答案覆盖关键词即可。", + "source": null, + "related": [] + }, + { + "id": "sa-005", + "type": "short_answer", + "difficulty": 4, + "tags": [ + "三色标记", + "并发标记", + "漏标", + "SATB" + ], + "question": "详细说明三色标记法(Tricolor Marking)的实现原理(白/灰/黑三种颜色的含义与转移过程),并分析并发标记过程中可能出现的『漏标』与『多标』问题,说明各自的来源及解决方案(如 SATB、写屏障、增量更新)。", + "answer": "三色标记法是一种用于增量/并发可达性分析的着色约定,把每个对象标记为三种颜色:\n\n一、颜色含义\n1. 白色(White):尚未被访问到的对象,初始所有对象都是白色;标记结束后仍为白色的即为不可达垃圾。\n2. 灰色(Gray):已被扫描访问到(已标记),但其引用的子对象还没有全部扫描完,灰色对象是待继续遍历的工作队列。\n3. 黑色(Black):该对象及其所有子引用都已被扫描完毕,不会再被遍历。\n\n二、标记过程\n1. 将 GC Roots 标记为灰色,放入工作栈。\n2. 循环弹出灰色对象,扫描其引用的所有子对象:若子对象为白色则将其变灰;该父对象全部子对象扫描完后变黑。\n3. 重复直到栈为空,此时剩余白色对象即为不可达垃圾,被回收。\n\n三、并发标记的问题\n由于标记阶段与用户线程并发,用户线程会修改引用,带来两个问题:\n\n1. 漏标(Missing Mark):应存活的对象被误判为白垃圾被错误回收,造成引用悬空(最严重)。经典触发条件是:黑色对象 A 新增了对白色对象 B 的引用,同时原本指向 B 的灰色对象 C 又删除了对 B 的引用。因为黑色对象不会再被扫描,B 被永久漏掉。\n- 解决:使用写屏障,保证黑色对象不会新增指向白色对象的引用。两种策略:\n- 写后记录(增量更新 Incremental Update):当黑色对象 A 的引用被修改时,把新指向的白对象 B 重新置灰,下一轮再扫描,CMS 采用。\n- 写前记录(SATB 起始快照 Snapshot-at-the-Beginning):在删除旧引用时,把旧对象标记为灰色(无论后续是否删除),G1 采用。\n\n2. 多标(Floating Garbage / 浮动垃圾):本应死亡的白色对象,在标记途中仍被活跃灰对象引用,或并发期间新产生的垃圾,导致本应回收的对象被标记成存活而延时回收。多标只是标记过多,不影响正确性(不会误回收存活对象),是并发收集可接受的少量延迟回收。\n\n四、保障结论\n通过写屏障正确保证黑色对象不可新增指向白色对象的引用,即可确保不漏标;SATB 与增量更新是两种解决漏标的写屏障策略,G1 默认 SATB,CMS 默认增量更新。", + "keywords": [ + "三色标记", + "白色灰色黑色", + "漏标", + "多标", + "写屏障", + "SATB", + "增量更新", + "浮动垃圾" + ], + "scoring_rubric": "三色含义与标记过程正确 3 分;漏标原理(需答出灰色转黑后黑色新增引用白色的必要条件)3.5 分;解决办法(写屏障、SATB 与增量更新的对照)+多标与浮动垃圾 3.5 分。总分 10 分。", + "explanation": "本题考查并发标记的线头。关键是以「A 黑引用 B 白 + B 的灰色引用被删除」讲清楚漏标的必要条件,并对应记忆 G1 用 SATB、CMS 用增量更新作对照。", + "source": null, + "related": [] + }, + { + "id": "sa-006", + "type": "short_answer", + "difficulty": 3, + "tags": [ + "标记清除", + "标记压缩", + "复制算法", + "算法对比" + ], + "question": "对比三种基本垃圾收集算法——标记-清除(Mark-Sweep)、标记-压缩(Mark-Compact)、复制(Copying)算法——各自原理、优缺点各列出 2 条,以及适合的场景。", + "answer": "一、标记-清除(Mark-Sweep)\n- 原理:先标记所有存活对象,再统一清除所有未被标记的对象,释放其空间。\n- 优点:(1) 实现最简单直接;(2) 不移动对象,无拷贝搬移开销。\n- 缺点:(1) 产生大量内存碎片,空闲空间不连续,难以分配大对象;(2) 需要扫描整个堆的存活与死亡对象,工作量随堆增大而上升(效率依赖堆大小)。\n- 适用:适合存活对象多、堆小、对碎片要求不高的场景(常为老年代的粗糙版本)。\n\n2. 标记-压缩(Mark-Compact)\n- 原理:先标记存活对象,再存活对象向堆的一端搬移压缩,使它们连续排列,然后清理边界之外的区域,并更新所有引用。\n- 优点:(1) 消除内存碎片,空间连续可分配大对象;(2) 不浪费额外空间。\n- 缺点:(1) 移动对象需更新所有引用,搬移成本高,耗时可能较长;(2) 实现复杂度高,STW 可能较长。\n- 适用:老年代(存活率高,复制算法不划算)。\n\n3. 复制算法(Copying / Mark-Copy)\n- 原理:把可用内存等分为两块(或 Eden + Survivor),每次只用其中一块;回收时把存活对象复制到另一块连续空间,然后把当前块整体清空。\n- 优点:(1) 复制后空间连续无碎片,分配高效;(2) 只遍历存活对象,存活率低时效率极高,复制即回收。\n- 缺点:(1) 需要预留等大的额外空间(即内存利用率减半);(2) 存活率高时复制量大、效率下降。\n- 适用:年轻代(存活率低、对象短命)。\n\n总结:复制算法适合年轻代,标记-存在与标记-压缩适合老年代;碎片/存活率是选择算法的核心权衡。", + "keywords": [ + "标记清除", + "标记压缩", + "复制算法", + "内存碎片", + "存活率", + "复制", + "算法效率", + "适用范围" + ], + "scoring_rubric": "每种算法:原理 1 分 + 优缺点各 2 分 + 适用场景 1 分,三种合计近 12 分按 10 分制折算;能较横向比较(复制→年轻代、清除→碎片、压缩→老年代)与碎片/存活率权衡逻辑更柔。", + "explanation": "三大基本算法的本质区别在于:清除浪费空间产生碎片、压缩重排消灭碎片、复制以空间换时间(只接触存活对象)。难度偏基础,重点考察对比思维。", + "source": null, + "related": [] + }, + { + "id": "sa-007", + "type": "short_answer", + "difficulty": 3, + "tags": [ + "内存碎片", + "碎片化", + "分配", + "解决方式" + ], + "question": "什么是内存碎片(Memory Fragmentation),它是如何产生的?会造成什么危害?请列出至少 3 种解决或缓解碎片问题的方案。", + "answer": "1. 定义:内存碎片指堆上的空闲内存被分割成大量不连续、零散的小块,导致虽然总的空闲内存足够,却找不到一块连续的足够空间来分配对象(尤其大对象)。分两类产生原因:内容累计分隔块的两类,碎片含内碎片与外部碎片。\n\n2. 产生原因:\n1. 主要源于使用标记-清除等不定长分配方式:每次只释放已死亡所占的空洞,长期下来空间被切成大量小块。\n2. 对象不断分配与释放、大小不一,缺乏整理;或分配器策略设计不当。\n\n3. 造成的危害:\n1. 无法每次分配大对象,GC 提前触发或分配失败,进而退化升级为代价高昂的 Full GC / 压缩 GC。\n2. 降低堆利用率,分配需寻找合适空洞,降低整体分配速度。\n3. 对实时/低延迟系统造成难以预料的停顿与延迟抖动。\n\n4. 解决/缓解方案:\n1. 标记-压缩(Mark-Compact):把存活对象搬到一起,消除碎片。\n2. 复制算法(Copying):使用 Eden/Survivor 对称空间,回收时把存活对象复制到全新连续区,天然无碎片。\n3. Region/分区式大堆:如 G1、ZGC 把堆切成 Region,回收后把 Region 整理成连续的空白空间复用。\n4. 分配器优化:按对象大小分类的桶/分块管理、对象复用、内存池,减少碎片产生。\n5. 结合滚动/并发压缩的收集器(CMS 结合 CD、G1 换代)逐步整理。", + "keywords": [ + "内存碎片", + "碎片产生", + "标记压缩", + "复制算法", + "Region", + "Full GC", + "连续空间", + "内存池" + ], + "scoring_rubric": "概念 2 分;产生原因(清除造成不连续、分配释放)2 分;危害 ≈2 分;列出 3 种及以上方案各 1~2 分(压缩、复制、G1 Region)合计 8~10 分为满分。", + "explanation": "此题将『碎片』这一具体现象与算法利弊衔接:清除形成碎片、压缩与复制解决碎片是核心链路,解决方案可延伸至 G1/ZGC 的 Region 化设计。", + "source": null, + "related": [] + }, + { + "id": "sa-008", + "type": "short_answer", + "difficulty": 4, + "tags": [ + "ZGC", + "染色指针", + "读屏障", + "亚毫秒" + ], + "question": "为什么 ZGC(Z Garbage Collector)能实现亚毫秒级别的 GC 停顿?请从它的收集目标、关键机制(染色指针、读屏障、Region/内存布局)几个方面系统说明。", + "answer": "ZGC 是一款基于 Region 的并发垃圾收集器,设计目标是把 Stop-The-World 时间控制在 10ms 以内,很多场景可达亚毫秒。其停顿极短来自与 G1 截然不同的成套设计:\n\n1. 几乎全并发:ZGC 几乎把所有阶段(标记、转移、重映射)都与用户线程并发执行,仅极短的根本扫描、初次标记临界等阶段需要短暂停顿,且停顿主要取决于 GC Root 数量,几乎与堆大小无关。\n\n2. 染色指针(Colored Pointers / 彩色指针):ZGC 不把标记信息放进对象头,而是直接利用 64 位指针的高位段(含 Finalizable、Remapped、M0/M1 等标识位)来承载对象的 GC 状态。判断对象状态时无需访问对象、无需加锁,随指针读取原子完成,标记开销极低;这也是它支持超大堆(TB 级)的关键。\n\n3. 读屏障(Read Barrier / Load Barrier):当用户线程读取引用时,若目标对象的转移/重映射状态未满足,读屏障会在指令微操作内自动把该对象并发迁移(Move)或重新映射(Remap)到新位置再返回。这样对象迁移过程无需停止应用线程,读(解引用)这一件事本身完成了迁移,正确性与低停顿兼得。\n\n4. 并发压缩与重映射(Concurrent Relocation & Remapping):回收时把存活对象复制到新的空闲 Region,借助转发指针对旧地址映射到新地址;应用在读取时由读屏障触发重映射,转移过程全部并发完成,无 STW 式全局更新引用。\n\n5. 多分桶 Region 内存 = 分堆为多种规格的 Region 以降低内借助;回收后 Region 被整理复用。\n\n综合:ZGC 把标记信息注入指针、用读屏障把迁移与重映射都并发化、同时只依赖 GC Roots 数量的停顿,从而把 STW 压到亚毫秒。代价是额外的读写屏障与吞吐开销。", + "keywords": [ + "ZGC", + "染色指针", + "读屏障", + "亚毫秒停顿", + "并发回收", + "重映射", + "Region", + "GC Roots" + ], + "scoring_rubric": "答出并发化几乎所有阶段 2 分;染色指针(把标记写入指针高位段)2~3 分;读屏障并发迁移/重映射 2~3 分;Region+并发压缩与停顿随堆交互的结论 2~3 分。综合 9~10 分。", + "explanation": "ZGC 的低停顿是多个设计协奏的结果:染色指针(把 GC 状态写进指针)+ 读屏障(读取时完成迁移)+ 并发重映射。抓住『几乎全并发、停顿只依赖根数量』即可把握本质,注意与 G1 的定位区分。", + "source": null, + "related": [] + }, + { + "id": "sa-009", + "type": "short_answer", + "difficulty": 4, + "tags": [ + "G1", + "Region", + "可预测停顿", + "RSet", + "SATB" + ], + "question": "简述 G1(Garbage-First)的 Region 结构及核心概念,并重点解释 G1 是如何实现『可预测的暂停』的。(可涉及 Region、Collection Set、Remembered Set、按期收益选择回收区域等)", + "answer": "1. Region:\nG1 不再物理划分连续年轻代/老年代,而是把整个堆划分为大量相同大小的 Region(通常 1M~32M,为 2 的幂)。每个 Region 可为 Eden、Survivor、Old 或 Humongous(大对象)之一:\n- 对象分配在 Region 内,回收以 Region 为独立单元;\n- 超过一定规模的大对象(Humongous)占用连续多个 Region;\n- 年轻代/老年代变为 Region 的逻辑集合,比例可动态调整,回收集合更灵活。\n\n2. Remember Set(RSet)与 Card Table:\n- 每个 Region 维护一个 RSet,记录『哪些 Region 的对象引用了本区对象』(跨区引用反向记录)。\n- 回收某个 Region 时只需扫描其 RSet 记录的外部引用 + 本区根,而不必扫描全部堆,大幅缩小 GC 扫描范围。\n- 跨区引用的写入由写屏障记录进 Card Table/RSet。\n\n3. 可预测停顿:\n1. 用户可设定最大停顿目标(-XX:MaxGCPauseMillis,默认 200ms)。\n2. G1 通过全局并发标记统计每个 Region 的『垃圾占比』(存活率),回收价值按垃圾比例从高到低排序——Garbage First 语义。\n3. 每次收集由 Young GC / Mixed GC 组成:Young GC 清 Eden、少量回收;Mixed GC 额外回收垃圾价值高的 Old Region。\n4. 收集时按停顿预算(时间段)决定本次回收多少个 Region,选择 CSet(Collection Collection Set)时优先挑选回收性价比高的 Region,从而把停顿时间稳定控制在预算内,实现可预测的停顿。\n\n4. 并发标记配合 SATB 写屏障保证不漏标,RSet 提供快速引用定位。\n\n因此 G1 能以『回收价值高优先』为原则、配合 RSet 降低扫描成本、并按预算选择回收 Region,把停顿时间稳定控制在目标范围内,实现停顿可预测。", + "keywords": [ + "G1", + "Region", + "RSet", + "可预测停顿", + "Remember Set", + "CSet", + "MaxGCPauseMillis", + "回收收益" + ], + "scoring_rubric": "Region 划分与逻辑标签 3 分;讲清 RSet 作用(记录跨区引用,免全堆扫描)2 分;核心『按回收收益排序选 Region + 时间预算』3 分;能收敛到可预测停顿与 CSet/MaxGCPauseMillis 得 2 分。总分 10 分。", + "explanation": "G1 之所谓 Garbage-First 来源其整理化细分 Region 并量化每个 Region 垃圾占比,按收益+停顿预算动态选 CSet。理解 RSet(免全堆扫描)与按收益选择三环节,才能真正把握『可预测停顿』。", + "source": null, + "related": [] + }, + { + "id": "sa-010", + "type": "short_answer", + "difficulty": 3, + "tags": [ + "GC Roots", + "可达性", + "循环引用", + "泄漏" + ], + "question": "请列举 GC Roots(根对象)都有哪些?并解释为什么基于可达性分析的垃圾回收能正确判定对象的存活性,从而使得『循环引用』的两个对象也能被正确回收、不会造成内存泄漏(与引用计数的对比较)。", + "answer": "一、GC Roots 是可达性遍历的起始存活对象集合,常见的 GC Roots 包括:\n1. 虚拟机栈(栈帧中的本地变量表):线程栈帧中局部变量引用的对象。\n2. 本地方法栈中 JNI 引用的对象:Native 方法引用的全局、局部对象。\n3. 方法区中的静态属性/常量:static 字段引用的对象及其类型。\n4. 运行时常量池中的常量引用:例如字符串常量池中的字符串对象。\n5. 当前被 synchronized 加锁(monitor)的对象。\n6. 系统类加载器 C 一些长期常驻的类结构、被 JIT 编译占用的 etc。\n\n二、为什么循环引用也能被正确回收(对比引用计数)\n1. 引用计数的缺陷:它基于『是否还被其他对象引用』计数,当两个互相引用的对象(A↔B)再无外部引用时,计数器仍 ≥1、永不归零,导致它们永不被回收,造成内存泄漏。\n2. 可达性分析的判据:它不以『对象数量』为判据,而是判『能否从 GC Roots 根出发被访问到』——垃圾的定义是『从根不可达』。\n3. 因此,只要 A、B 组成循环引用且整体不被任何 GC Roots 直接或间接可达,系统从 Roots 出发就走不到这组对象,它们便作为『未标记(白色)』的孤岛整体被回收,回收后不形成泄漏。\n4. 结论:只要循环组是孤儿(岛孤岛),它就是可回收整体,可达性分析能安全地全部回收,从而彻底规避了引用计数的循环引用泄漏问题。只有真正连在来自根的引用链上的对象才被保留。", + "keywords": [ + "GC Roots", + "本地变量", + "静态变量", + "常量池", + "synchronized", + "可达性", + "循环引用", + "泄漏" + ], + "scoring_rubric": "GC Roots 能列全 4 种以上(栈变量、静态变量、常量池、monitor)、说明根可达判据与引用计数的差异、并准确解释循环孤儿岛会被整体回收,共给 8~10 分(逐条给分)。", + "explanation": "把握『GC 判定垃圾的唯一正确标准是从根可达』,从而理解计数法的循环引用死穴如何被根遍历机制自然化解——孤儿循环组作为整体回收即不泄漏。", + "source": null, + "related": [] + } + ] +} \ No newline at end of file diff --git a/topics/gc-special/gc-deep-dive/single_choice.json b/topics/gc-special/gc-deep-dive/single_choice.json new file mode 100644 index 0000000..3513549 --- /dev/null +++ b/topics/gc-special/gc-deep-dive/single_choice.json @@ -0,0 +1,202 @@ +{ + "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": [] + } + ] +} \ No newline at end of file diff --git a/topics/gc-special/gc-deep-dive/true_false.json b/topics/gc-special/gc-deep-dive/true_false.json new file mode 100644 index 0000000..6ba0e0c --- /dev/null +++ b/topics/gc-special/gc-deep-dive/true_false.json @@ -0,0 +1,118 @@ +{ + "topic": "gc-deep-dive", + "type": "true_false", + "schema_version": "1.0.0", + "generated": "2026-09-03T00:00:00Z", + "questions": [ + { + "id": "tf-001", + "type": "true_false", + "difficulty": 2, + "tags": ["gc", "语言对比", "自动内存管理"], + "question": "判断以下陈述是否正确:C/C++ 需要程序员手动分配和释放内存(如 malloc/free 或 new/delete);Rust 并没有采用传统 GC,而是通过所有权(ownership)与借用检查在编译期自动管理内存;而 Go 则依赖自动垃圾回收(GC)。", + "answer": true, + "explanation": "正确。C/C++ 是无 GC 语言,必须手动管理堆内存,容易造成泄漏与悬垂指针。Rust 不属于传统垃圾回收模型,其内存安全由所有权与生命周期系统在编译期保证,运行时并不寄生 GC。Go 则内置了 GC,由运行时负责回收不再使用的堆对象。", + "source": null, + "related": [] + }, + { + "id": "tf-002", + "type": "true_false", + "difficulty": 1, + "tags": ["gc", "lang", "go"], + "question": "判断以下陈述是否正确:Go 语言和 C/C++ 一样,运行时都没有自动垃圾回收机制,必须由程序员手动调用 free 等接口释放内存。", + "answer": false, + "explanation": "错误。Go 是带 GC 的语言,其 runtime 会自动回收不再引用的堆对象,程序员无需(也无法直接)手动调用 free。C/C++ 才是需要手动管理的。这也是 Go 通常被称为带自动 GC 语言的原因。", + "source": null, + "related": [] + }, + { + "id": "tf-003", + "type": "true_false", + "difficulty": 2, + "tags": ["gc", "代价", "stop-the-world"], + "question": "判断以下陈述是否正确:启用 GC 会引入运行时代价,包括发生 STW(stop-the-world)暂停、额外约 5%~20% 的内存开销,以及额外的 CPU 开销。", + "answer": true, + "explanation": "正确。GC 的核心代价来自三个方面:1) 标记/整理阶段常需要 STW 暂停用户线程;2) 分代/复制区等机制通常需要额外 5%–20% 左右的堆内存作为缓冲或预留;3) 标记、写屏障与并发更新等日常操作会消耗 CPU。这三项是评估 GC 开销的基本指标。", + "source": null, + "related": [] + }, + { + "id": "tf-004", + "type": "true_false", + "difficulty": 2, + "tags": ["gc", "mark-sweep", "碎片"], + "question": "判断以下陈述是否正确:标记-清除(Mark-Sweep)算法回收时不会移动存活对象,因此能保持内存紧凑、不会产生内存碎片。", + "answer": false, + "explanation": "错误。标记-清除算法只做标记与回收,并不移动对象,因此存活对象之间的空闲块会零散分布,产生大量内存碎片。这是 Mark-Sweep 的固有缺点,也是它不适合老年代等“大对象存活多”场景的原因;通常需要搭配压缩或内存合并策略来缓解。", + "source": null, + "related": [] + }, + { + "id": "tf-005", + "type": "true_false", + "difficulty": 4, + "tags": ["gc", "copying", "复制算法", "老年代"], + "question": "判断以下陈述是否正确:复制(Copying)算法即使把存活对象拷贝到另一半空间,其拷贝成本与存活对象数量无关,因此非常适合在老年代等存活率很高的区域使用。", + "answer": false, + "explanation": "错误。Copying 算法需要把存活对象全部复制到另一半空间,复制工作量与存活对象数量成正比。当对象存活率高(如老年代)时,复制开销巨大、几乎每个对象都要被搬移,因此复制算法通常只用于年轻代(存活率低的区域),不适合对象存活率高的老年代(那里多用标记-压缩)。", + "source": null, + "related": [] + }, + { + "id": "tf-006", + "type": "true_false", + "difficulty": 3, + "tags": ["gc", "mark-compact", "标记-压缩", "碎片"], + "question": "判断以下陈述是否正确:标记-压缩(Mark-Compact)算法在回收的同时移动存活对象使内存紧凑,从而彻底消除碎片,但代价是要移动对象并更新所有引用,因此速度比不移动对象的标记-清除慢。", + "answer": true, + "explanation": "正确。Mark-Compact 在标记存活对象后,将它们紧凑到一端并更新所有指向它们的引用,消除碎片、有利于大对象连续分配。但压缩本身的移动量与引用更新会产生额外开销,所以速度慢于不做移动的 Mark-Sweep。正因如此,压缩算法常用于存活率较高的老年代。", + "source": null, + "related": [] + }, + { + "id": "tf-007", + "type": "true_false", + "difficulty": 3, + "tags": ["gc", "分代", "minor-gc", "eden"], + "question": "判断以下陈述是否正确:分代收集器中,当年轻代的 Eden 区被填满时会触发 Minor GC,而这类针对年轻代的 Minor GC 通常是一个需要停顿(stop-the-world)的事件。", + "answer": true, + "explanation": "正确。分代 GC 会把新建对象优先分配进 Eden,Eden 满时触发 Minor GC,把仍存活的对象复制到 Survivor 区,超龄后再升入老年代。传统多数实现中,Minor GC 通常是一个 stop-the-world 事件(即使现代收集器如 G1 也会产生暂停),在这段时间内应用线程会被暂停。", + "source": null, + "related": [] + }, + { + "id": "tf-008", + "type": "true_false", + "difficulty": 3, + "tags": ["gc", "G1", "默认收集器", "region"], + "question": "判断以下陈述是否正确:G1(Garbage-First)自 JDK 8 起就成为 HotSpot 的默认收集器,并把堆划分为多个相同大小的 Region。", + "answer": false, + "explanation": "错误。G1 确实基于 Region 并把堆划分为若干相同大小的 Region,但它直到 JDK 9 才成为 HotSpot 的默认收集器;JDK 8 默认的仍是 Parallel 组合。因此“自 JDK 8 起即是默认”的说法不成立。G1 同时支持通过 -XX:MaxGCPauseMillis 设置可预测的停顿时间目标。", + "source": null, + "related": [] + }, + { + "id": "tf-009", + "type": "true_false", + "difficulty": 4, + "tags": ["gc", "CMS", "碎片", "concurrent-mode-failure"], + "question": "判断以下陈述是否正确:CMS 采用标记-清除算法因此不会产生内存碎片,也正因如此它不会出现 Concurrent Mode Failure 问题。", + "answer": false, + "explanation": "错误。CMS 正是基于 Mark-Sweep 的收集器,因此会保留内存碎片(碎片过多时后续 Full GC 需要额外的空间整理);同时,当并发标记阶段老年代空间被耗尽时,CMS 会降级回退到 Serial Old 的全量停顿 GC,这称为 Concurrent Mode Failure。因此两个说法都不成立。", + "source": null, + "related": [] + }, + { + "id": "tf-010", + "type": "true_false", + "difficulty": 5, + "tags": ["gc", "三色标记", "漏标", "GC-Root"], + "question": "判断以下陈述是否正确:在三色标记法中,“漏标”的危害大于“多标”——漏标可能把存活对象误当作死对象回收,从而引发程序崩溃;同时,只有静态变量、JNI 全局引用等根对象出发的可达对象才由 GC Roots 判定存活,而堆中普通对象之间的引用并不是 GC Roots。", + "answer": true, + "explanation": "正确。三色标记把对象分为白/灰/黑三类:白色表示尚未被扫描、可回收;黑色表示已扫描且其所有子对象都处理完毕、安全存活。若因并发或写屏障问题造成漏标,使存活对象仍保持白色,就会在清扫阶段被错误回收,导致程序崩溃,因此漏标比多标更危险(漏标会误回收活对象,而多标至多保留垃圾,不会破坏程序)。而 GC Roots 是堆外能直达堆的出发点,包括静态字段引用、JVM 栈帧(局部变量)持有的对象、JNI 全局引用等;堆中普通对象之间的引用只是对象图的一条边,本身不是 GC Roots。本题两个子命题均正确,故整体正确。", + "source": null, + "related": [] + } + ] +} \ No newline at end of file diff --git a/topics/index.json b/topics/index.json index 054fc5c..a1ddb99 100644 --- a/topics/index.json +++ b/topics/index.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "updated": "2026-09-02", + "updated": "2026-09-03", "topics": [ { "slug": "qunar-ai-fullstack", @@ -197,6 +197,29 @@ } } ] + }, + { + "slug": "gc-special", + "name": "垃圾回收专题", + "description": "垃圾回收(GC)专题:GC 概念与代价、内存碎片与算法、分代 GC、回收器对比、GC Roots、三色标记法", + "subtopics": [ + { + "slug": "gc-deep-dive", + "name": "GC 综合深入", + "description": "垃圾回收专题:GC 概念、内存碎片与算法、分代 GC、回收器对比、GC Roots、三色标记法", + "path": "topics/gc-special/gc-deep-dive", + "stats": { + "total": 50, + "by_type": { + "single_choice": 10, + "fill_blank": 10, + "true_false": 10, + "multiple_choice": 10, + "short_answer": 10 + } + } + } + ] } ] -} \ No newline at end of file +}