永久代存在内存限制:永久代大小固定(通过 -XX:MaxPermSize 配置),难以动态调整,易因类信息过多导致 PermGen OOM。
元空间使用本地内存:元空间默认大小仅受操作系统内存限制,可动态扩展,减少内存溢出风险。
简化类加载器管理:元空间与类加载器生命周期绑定,类加载器回收时,其加载的类信息随元空间一起释放,降低内存泄漏概率。
适配 JVM 模块化发展:为 Java 9 模块化系统(如 Jigsaw)铺路,更灵活地支持类数据的动态加载与卸载。
基于标记 - 复制算法优化:新生代采用标记 - 复制算法,Eden 区用于新对象分配,S0 和 S1(Survivor 区)用于存活对象复制,避免内存碎片。
减少存活对象复制开销:大多数新对象在 Eden 区创建后很快被回收,仅少数存活对象复制到 Survivor 区,降低复制成本。
控制对象晋升老年代的频率:对象在 S0 和 S1 之间多次复制(默认 15 次,由 -XX:MaxTenuringThreshold 控制)后进入老年代,避免短期存活对象占用老年代空间。
提升 GC 效率:分区域后,Minor GC 仅扫描 Eden 和一个 Survivor 区,缩小扫描范围,缩短 STW 时间。
定义:是垃圾收集器(如 CMS、G1)用于并发标记阶段的对象标记算法,通过三色标记区分对象的可达性状态。
三色划分:
白色:未被标记的对象(默认状态,可能为垃圾)。
灰色:自身已被标记,但引用的子对象未完全标记的对象。
黑色:自身及引用的子对象均已标记完成的对象(存活对象)。
核心流程:
初始标记:标记 GC Roots 直接关联的对象为灰色,触发短暂 STW。
并发标记:遍历灰色对象,标记其子对象为灰色,自身转为黑色,与用户线程并行执行。
最终标记:修正并发标记期间因用户线程操作导致的标记偏差(如漏标),触发短暂 STW。
解决的问题:允许标记过程与用户线程并发执行,减少 STW 时间,但需处理 “漏标” 问题(通过增量更新或原始快照机制)。
young GC(Minor GC):仅回收新生代(Eden + Survivor)的垃圾,触发频繁,STW 时间短。
old GC(Major GC):仅回收老年代的垃圾,多为 CMS 收集器的并发标记清除阶段,STW 时间较长(但比 Full GC 短)。
full GC:回收整个堆内存(新生代 + 老年代 + 方法区),STW 时间最长,可能伴随内存碎片整理。
mixed GC:仅 G1 收集器支持,同时回收新生代和部分老年代(垃圾比例高的 Region),平衡回收效率和 STW 时间。
主要触发条件:Eden 区内存不足,无法为新对象分配内存时,触发 young GC 回收 Eden 区垃圾,将存活对象复制到 Survivor 区。
其他因素:
大对象直接进入老年代,若 Eden 区剩余空间不足,可能提前触发 young GC。
Survivor 区空间不足,存活对象需晋升老年代时,可能伴随 young GC。
老年代内存不足:老年代无法为晋升的对象(如 Survivor 区溢出的对象、大对象)分配内存时。
方法区(元空间)内存不足:加载的类信息、常量等超过元空间限制时(JDK 8 前为永久代)。
显式调用 System.gc():虽为建议性调用,但多数 JVM 会触发 Full GC(可通过 -XX:+DisableExplicitGC 禁用)。
CMS 收集器出现 Concurrent Mode Failure:并发回收老年代时,内存不足导致回收失败,触发 Full GC。
G1 收集器的混合回收无法满足暂停目标:当老年代占用率超过阈值(默认 45%),可能触发 Full GC。
定义:PLAB(Promotion Local Allocation Buffer,晋升本地分配缓冲区)是 G1 收集器中,每个 GC 线程在老年代分配的私有缓冲区,用于存储从新生代晋升的对象。
作用:减少多线程晋升对象时的锁竞争,提升晋升效率(类似 TLAB 对新生代分配的优化)。
工作原理:GC 线程晋升对象时,优先使用自己的 PLAB 空间,不足时从老年代公共区域分配,避免频繁加锁。
定义:是 CMS 收集器在并发标记或并发清除阶段,老年代内存不足导致的失败。
具体原因:
并发回收期间,用户线程持续创建对象,老年代剩余空间不足以容纳新对象或晋升对象。
晋升担保失败:新生代 GC 时, Survivor 区溢出的对象需晋升老年代,但老年代剩余空间不足。
后果:触发 Full GC(通常由 Serial Old 收集器执行),导致长时间 STW。
设计妥协:CMS 主要优化并发回收效率,未对 Full GC 路径做多线程优化,默认使用 Serial Old 收集器执行 Full GC(单线程,标记 - 整理算法)。
避免复杂度:Full GC 需整理老年代内存(解决碎片),多线程整理需处理对象移动的同步问题,实现复杂。
历史原因:早期 JVM 中,Serial Old 是老年代最稳定的单线程收集器,作为 CMS 失败后的备用方案,确保回收可行性。
设计目标冲突:ParNew 是为配合 CMS 设计的多线程新生代收集器(侧重响应时间),而 Parallel Old 是侧重吞吐量的老年代收集器,二者优化目标不一致。
实现机制不兼容:不同收集器对内存布局、对象标记、晋升机制的实现不同(如记忆集维护方式、GC 日志格式),难以协同工作。
官方适配限制:JVM 仅支持特定组合(如 ParNew + CMS、Parallel Scavenge + Parallel Old、G1 单收集器全区域回收),确保稳定性和性能。
基于分代假设:新生代对象存活周期短,仅需扫描新生代(Eden + Survivor),无需扫描老年代。
记忆集(Remembered Set):维护老年代对新生代对象的引用,新生代 GC 时仅需扫描记忆集中的引用,避免全堆扫描老年代。
卡表(Card Table):作为记忆集的实现,将老年代划分为大小固定的卡页,当老年代对象引用新生代对象时,标记对应卡页为 “脏页”,GC 时仅扫描脏页中的引用。
CMS 的记忆集:
作用:记录老年代对新生代的引用,避免 Minor GC 扫描全老年代。
实现:采用卡表(Card Table),每个卡页对应老年代的一块内存(通常 512B),引用新生代对象时标记为脏页。
维护:通过写屏障(Write Barrier)监控老年代对象的引用更新,及时标记脏页。
G1 的记忆集:
作用:记录不同 Region 之间的引用(如老年代 Region 对新生代 Region、大对象 Region 的引用)。
实现:每个 Region 对应一个记忆集,存储引用当前 Region 的其他 Region 卡页信息。
维护:通过写屏障和快照日志(SATB)记录引用变化,支持并发标记时的引用跟踪。
年轻代对象存活周期短:年轻代对象很快被回收,其对老年代的引用生命周期短,维护此类记忆集性价比低。
混合回收机制:G1 采用混合回收(同时回收新生代和部分老年代),老年代 Region 会被定期扫描,无需单独维护年轻代到老年代的引用记录。
减少内存开销:记忆集本身占用额外内存,省略年轻代到老年代的记忆集,可降低内存消耗,简化实现。
CMS 的并发正确性保障:
初始标记和重新标记阶段 STW,确保 GC Roots 标记准确。
并发标记阶段使用 “增量更新” 机制:当对象引用发生变化(如删除旧引用、添加新引用),通过写屏障记录变化,重新标记阶段修正这些引用。
G1 的并发正确性保障:
初始标记和最终标记阶段 STW,确保标记基础正确。
并发标记阶段使用 “原始快照(SATB)” 机制:记录并发标记开始时的对象引用快照,即使引用后续变化,仍以快照为准,避免漏标,最终标记阶段修正偏差。
无内存碎片:G1 采用标记 - 复制算法整理存活对象,老年代无碎片;CMS 用标记 - 清除算法,易产生碎片。
可预测的 STW 时间:G1 支持设置最大暂停目标(-XX:MaxGCPauseMillis),优先回收垃圾多的 Region,控制 STW 时间;CMS 无法精确控制。
全区域回收能力:G1 可同时回收新生代和老年代,无需配合其他收集器;CMS 仅回收老年代,需与 ParNew 配合。
内存利用率更高:G1 动态调整新生代和老年代比例,适应不同应用场景;CMS 内存布局固定,灵活性差。
更适合大堆内存:G1 对大堆(如 4GB 以上)的管理效率优于 CMS,支持 TB 级内存。
定义:是 G1 和 ZGC 等收集器用于并发标记的写屏障机制,用于记录对象引用的变化日志。
作用:在并发标记阶段,当用户线程修改对象引用(如 a.b = c)时,写屏障将旧引用(a.b 原来的值)记录到日志中,确保标记过程不遗漏对象。
与 SATB 配合:G1 的 logging write barrier 是原始快照(SATB)机制的核心,通过记录引用变化,最终标记阶段可根据日志修正标记结果,保障并发正确性。
初始标记(Initial Mark):
STW 阶段,标记 GC Roots 直接关联的对象(如虚拟机栈中的引用),标记完成后立即恢复用户线程。
并发标记(Concurrent Mark):
与用户线程并行,从初始标记的对象出发,遍历所有可达对象并标记,同时通过 SATB 机制记录引用变化。
最终标记(Final Mark):
短暂 STW 阶段,处理并发标记期间积累的引用变化日志,修正标记结果,确保无漏标。
筛选回收(Live Data Counting and Evacuation):
STW 阶段,计算每个 Region 的垃圾比例,按优先级排序,回收垃圾比例高的 Region(新生代优先,老年代按需混合回收),将存活对象复制到新 Region,实现内存整理。
初始标记(Initial Mark):
STW 阶段,标记 GC Roots 直接关联的老年代对象,速度快(仅标记直接引用)。
并发标记(Concurrent Mark):
与用户线程并行,从初始标记的对象出发,遍历老年代所有可达对象并标记。
重新标记(Remark):
短暂 STW 阶段,修正并发标记期间因用户线程操作导致的标记偏差(如新增引用、删除引用),使用增量更新机制处理。
并发清除(Concurrent Sweep):
与用户线程并行,回收所有未标记的垃圾对象(老年代),不整理内存,可能产生碎片。
定位:JDK 11 引入的低延迟垃圾收集器,支持 TB 级堆内存,STW 时间控制在毫秒级甚至微秒级。
核心技术:
着色指针(Colored Pointers):利用指针未使用的位存储对象标记信息(如存活、回收中),无需额外内存存储标记数据。
读屏障(Load Barrier):当用户线程读取对象引用时,检查指针颜色,处理并发移动对象的一致性问题。
回收流程:
并发标记:遍历对象图标记存活对象,无 STW。
并发预备重定位:选择待回收的 Region,准备移动对象。
并发重定位:移动存活对象到新 Region,通过读屏障确保用户线程访问正确地址。
并发清理:回收空 Region,供新对象分配。
适用场景:对延迟要求极高的应用(如金融交易、实时数据分析),大内存环境。
减少 STW 时间:确保 GC 暂停时间不超过应用可接受的阈值(如 Web 应用通常要求 < 100ms),避免影响用户体验。
降低 GC 频率:减少 GC 对 CPU 和内存资源的占用,提升应用吞吐量(用户线程执行时间占比)。
避免内存溢出(OOM):合理配置堆内存大小、代际比例、元空间大小,确保应用稳定运行。
平衡资源占用:在内存、CPU 资源有限的情况下,优化 GC 参数,使 GC 开销(如 CPU 使用率)控制在合理范围。
适配应用场景:根据应用类型(如吞吐量优先的后台任务、延迟优先的交互应用)调整 GC 收集器和参数,最大化性能。