前言
Shenandoah收集器是不被Oracle公司承认的,所以只有在OpenJdk才可以使用,在OracleJdk中是没法使用的。Shenandoah收集器相比交于血统纯正的ZGC收集器,更像是G1的继承者。他们很多方面都比较相似,而且他们甚至还共享了一段代码。
Shenandoah收集器简介
Shenandoah收集器和G1类似,也是基于region的堆内存布局进行垃圾回收,同样有这放大对象的区域,默认回收策略,也是优先回收有价值的region。但是他在内存回收上面和G1有很多不同。
1.支持并发的整理回收算法。
2.默认不使用分代集
3.不使用记忆集,改为连接矩阵
Shenandoah收集器工作过程
Shenandoah收集器的工作过程大致分为九个过程
1.初始标记
与G1一样,都是简单标记和GCroot直接关联的对象
2.并发标记
与G1一样,遍历对象图,标记全部可达对象,和用户线程一起并发的
3.最终标记
与G1一样,处理剩余的stable
4.并发清理
清理一个存活对象都没有的region
5.并发回收
G1之所以做不到并发回收,是因为如果在移动对象的同时可以修改对象的话,移动完之后,对象还是指向的就地址,很难一瞬间改过来。Shenandoah收集器怎么解决这个问题的呢,它是通过读屏障和转发指针来解决的。(转发指针讲起来很麻烦,稍后再讲)
6初始引用更新
建立线程集合点,确认所有线程都已完成分配给对象的移动任务
7.并发引用更新
回收阶段结束后,把对象的指针从旧对象改为新对象。
8.最终引用更新
修正GcRoot的引用
9.并发清理
回收region空间
Brooks 转发指针
Brooks转发指针和java的句柄定位有点类似,不过差别是句柄通常会统一存储在句柄池中,而转发指针是分散存放在每一个对象头前面。这样的设计,决定的了必然会出现多线程竞争。
1.收集器线程复制了新的对象副本
2.用户线程更新对象的某个字段
3.收集线程更新转发指针的引用值为新副本地址
如果不做保护措施的话,让事件二发生在一和三之间,将导致用户线程对对象的变更,在一三之间,将导致的结果就是用户线程对对象的变更发生在旧对象上。所以必须对用户的访问操作采取同步措施,让收集线程和用户线程,只能有一个成功,另一个必须等待。实际上是通过cas操作来保证并发的。
对象访问是很频繁的,如果都加上读写屏障,消耗很大,所以Shenandoah收集器将读写的屏障,改为引用读写的屏障,也就是只对对对象引用类型数据修改加屏障。而不是整体加,就避免不是访问指针,而是访问其他数据也加屏障。
总结
Shenandoah收集器整体来说还是很不错的,但是因为不被官方支持,兼容性方面还是有很大的问题的。他的停顿时间虽说比其他几款收集器有了质的飞跃,但是还是没有实现控制在10毫秒之内,吞吐量方面也有了很大的下降,总运行时间也是十分的长的,但是依然是一款十分不错的收集器。