太老的收集器就不做笔记了,想了解的可以看《深入理解JVM》
只要有垃圾收集和用户线程并行的,就必须要考虑新对象创建的问题;
三个重要指标:
- 内存占用、吞吐量、延迟;
- 与CAP协议类似的,三者不可兼得。。。
垃圾收集器追求的变化:
- 之前追求一次把整个java堆清理干净;
- 从G1开始追求收集的速度赶上对象分配的速度;
一、ParNew+CMS
ParNew
Serial的多线程版本
CMS
- 初始标记:枚举根节点,stw
- 并发标记
- 重新标记:stw
- 并发清除
缺点:
- 无法处理浮动垃圾:并发失败有可能导致full GC;
- 使用标记-清除,有大量空间碎片,多次收集后需要进行一次内存整理;
- 如何解决并发的可达性分析?增量更新+重新标记
二、G1
思想
- 面向局部收集
- 基于Region的 内存布局形式
- G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的java堆分为多个大小相等的region,每一个region根据需要扮演新生代的eden空间,Survivor空间或者老年代空间。
- 取值1~32M,-XX:G1HeapRegionSize
Humongous
属于特殊的一种Region区域,大部分被G1认为是老年代。专门用来存储大对象(超过一个Region容量一半大小),此时这些对象会被存放在N个连续的Humongous Region中。
处理思路
- 将Region作为单次回收的最小单元,每次收集空间是Region大小的整数倍;
- 这样可以有计划的避免整个Java堆中进行全区域的垃圾收集;
- G1收集器跟踪各个Region里面的垃圾堆积“价值”大小;
- 后台维护一个优先级队列,每次根据用户设定允许的收集停顿时间,优先处理回收价值最大的Region;
运行过程
- 初始标记
- stw、枚举GC Roots、修改TAMS指针;
- 并发标记
- 可达性分析,STAB记录并发时引用发生变化的对象
- 最终标记
- stw、重新对STAB的对象进行可达性分析
- 筛选回收
- stw、复制存活对象到空Region,删除整个旧Region
存在的问题
- 由于多个Region打破了固定大小的分代区域划分,G1在记忆集的维护上要耗费更大的内存空间(10%~20%),每个Region都要维护一个记忆集(哈希表);
- 如何实现并发可达性分析?
- 原始快照、STAB算法(两个TAMS)
三、比较CMS和G1
- 算法实现:
- CMS标记清除
- G1标记整理(整体)、标记-复制(Region)
- 垃圾收集内存占用:G1由于对每个Region都要维护卡表,高于CMS;
- 并发可达性分析处理:
- CMS:增量更新
- G1:原始快照,STAB算法(两个TAMS)
- 写屏障
-
- CMS:写后屏障维护卡表
- G1:写前屏障跟踪并发指针变化,写后屏障维护卡表(队列、异步)