shenandoah并发回收中,对象副本按需、渐进生成:gc线程在回收集内逐个复制存活对象至新region,并用cas原子更新brooks转发指针;用户线程通过读屏障自动跳转至新副本,确保访问语义一致,原对象仅作跳转载体直至引用更新完成。

Shenandoah并发回收中,对象副本不是“预生成”或“统一创建”的,而是按需、渐进、受控地在用户线程持续运行时动态生成的。其核心逻辑围绕存活对象复制 + 转发指针 + 读写屏障协同展开,目标是让新旧副本共存期间语义不丢失、访问不中断。
副本生成触发条件
副本只在对象被判定为“属于回收集(Collection Set)且存活”后才启动生成。这个判定发生在并发标记与最终标记阶段完成之后,由GC线程根据Region回收价值选出待回收区域,并确认其中哪些对象仍可达。未被选入回收集的Region,其对象不参与副本生成;已死亡对象直接被忽略,不复制也不留副本。
副本生成过程:复制+原子更新
当GC线程开始处理某个回收集Region时:
- 逐个扫描该Region内已被标记为存活的对象
- 为其在空闲Region中分配等大小的新内存空间
- 将原始对象内容(除对象头外)完整复制过去
- 用CAS原子操作更新原对象头前的Brooks转发指针,使其指向新副本地址
这一过程对每个对象独立进行,不要求整个Region同步完成。一个Region中可能部分对象已复制并更新指针,其余尚未处理——这正是并发性的体现。
副本如何被用户线程感知
用户线程不会主动“找副本”,而是通过JVM插入的读屏障自动跳转:
- 每次通过引用访问对象字段前,触发读屏障
- 读屏障检查该引用所指对象头的Brooks指针
- 若指针已更新(非指向自身),则透明重定向到新副本地址
- 若指针未更新(仍指向自身),说明尚未复制,直接读取原对象
因此,同一个对象在不同时间点、被不同线程访问,可能读到原地址或新地址,但结果始终一致——因为复制是原子内容拷贝,且写屏障确保所有字段写操作都落在新副本上(一旦指针更新)。
副本生命周期与释放
副本生成后并不立即取代原对象:
- 原对象仍保留在旧Region中,直到并发引用更新阶段将其所有外部引用修正完毕
- 新副本从生成起即可被用户线程安全读写,无需等待其他对象就绪
- 旧Region在并发回收完成后即被标记为空闲,可被重新分配给新对象,与副本是否被完全引用无关
也就是说,副本不是“临时中转”,而是正式接管身份的新实例;原对象退化为仅用于兜底跳转的元数据载体,不再承载业务状态。











