面试官:说下你了解的Java收集器你该如何解答
在两个收集器之间存在连线,可以搭配使用
收集器所处的区域,表示它是属于新生代还是老年代收集器
不存在“万能”的收集器,选择的只是对具体应用最合适的收集器
收集器发展目标:
为消除或者降低用户线程因垃圾收集而导致停顿的时间
三项指标:
1.内存占用(Footprint)
2.2.吞吐量(Throughput)
3.3.延迟(Latency)
并行(Parallel):多条垃圾收集器线程同时运行,通常默认此时用户线程是处于等待状态
并发(Concurrent):收集器线程与用户线程同时运行,应用程序的处理的吞吐量受到一定影响
1.Serial收集器
JDK1.3.1之前,HotSpot虚拟机新生代收集器的唯一选择,单线程工作,进行垃圾收集时,必须暂停其他所有工作线程,直到收集结束
优点是:
高效而简单,额外内存消耗最小,单核处理器或处理器核心数较少的环境收集效率高,没有线程交互的开销,专心做垃圾收集
Serial/Serial Old
2.ParNew收集器
Serial收集器的多线程并行版本,JDK 7之前首选的新生代收集器,除Serial收集器外,只有它能与CMS收集器配合工作
-XX:+UseConcMarkSweepGC:
激活CMS后的默认新生代收集器
-XX:+/-UseParNewGC:
禁用或强制指定
jdk8声明废弃ParNew加Serial Old以及Serial加CMS这两组收集组合,jdk9完全取消,只能ParNew和CMS
单核处理器环境,效果比不上Serial收集器
多核高效率,
-XX:ParallelGCThreads:
限制垃圾收集的线程数
3.Parallel Scavenge
基于标记-复制算法的新生代收集器,多线程并行收集
与其他收集器尽可能地缩短垃圾收集时用户线程的停顿时间不同,关注点是达到一个可控制的吞吐量
两个参数用于精确控制吞吐量:
-XX:MaxGCPauseMillis:
最大垃圾收集停顿时间(停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的,会导致GC频繁)
-XX:GCTimeRatio:
设置吞吐量大小(用户代码时间与GC运行时间的倍数)
-XX:+UseAdaptiveSizePolicy:
动态调整其他参数以提供最合适的停顿时间或者最大的吞吐量(自适应的调节策略)
4.Serial Old/PS MarkSweep
Serial收集器的老年代版本,单线程,使用标记-整理算法
JDK 5以及之前与Parallel Scavenge搭配使用
作为CMS收集器发生失败时的后备预案,在并发收集发生ConcurrentMode Failure时使用
5.Parallel Old
Parallel Scavenge的老年代版本,支持多线程并发收集,基于标记-整理算法实现
JDK 6时才开始提供,Parallel Scavenge加Parallel Old搭配组合,吞吐量多线程并发
Parallel Scavenge/Parallel Old
6.CMS收集器
并发收集、低停顿
垃圾收集线程与用户线程(基本上)同时工作,基于标记-清除算法,无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作,
关注点:最短回收停顿时间
运作过程分为四个步骤:
1)初始标记(CMS initial mark)
标记GC Roots能直接关联到的对象,速度很快,需要 Stop TheWorld
2)并发标记(CMS concurrent mark)
从GC Roots的直接关联对象遍历整个对象图,这个过程耗时较长但不需要停顿用户线程,
可以与垃圾收集线程一起并发运行
3)重新标记(CMS remark)
修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(增量更新),停顿时间比初始标记阶段稍长,远比并发标记阶段的时间短,需要 Stop TheWorld
4)并发清除(CMS concurrent sweep)
清理删除掉标记阶段判断的已经死亡的对象,耗时较长,由于不需要移动存活对象,可以与用户线程同时并发的
耗时较长的阶段都可以与用户线程一起工作
缺点:
1.基于“标记-清除”算法实现,收集结束时会有大量空间碎片产生,大对象无法分配连续空间,触发Full GC
-XX:+UseCMS-CompactAtFullCollection: JDK 9开始废弃
收集器不得不进行Full GC时开启内存碎片整理过程,须移动存活对象,(在Shenandoah和ZGC出现前)无法并发,停顿时间变长
-XX:CMSFullGCsBefore-Compaction:
JDK 9开始废弃
CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入FullGC前会先进行碎片整理,默认值为0,表示每次进入Full GC时都进行碎片整理
2.无法处理浮动垃圾,可能出现“Con-current Mode Failure”失败而导致另一次完全Stop The World 的Full GC的产生,由于在垃圾收集阶段用户线程可以并发运行,须预留一部分空间供并发收集时的分配新对象(含有新垃圾即浮动垃圾,无法当前收集处理)
-XX:CMSInitiatingOccu-pancyFraction:
当老年代使用了多少占比的空间后就激活收集,越高,内存回收频率越低,但预留的内存无法满足分配新对象,会出现并发失败Concurrent Mode Failure
虚拟机启动后备预案:
冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,停顿时间变长
Concurrent Mark Sweep
7.Garbage First G1
面向局部收集的设计思路和基于Region的内存布局形式。CMS替代者
JDK 9宣告取代Parallel Scavenge加Parallel Old组合,CMS声明不推荐使用
Mixed GC模式:
衡量标准是哪块内存中存放的垃圾数量最多,回收收益最大,不再是它属于哪个分代
回收收益:
维护一个优先级列表,跟踪各个Region回收所获得的空间大小以及回收所需时间的经验值根据用户设定允许的收集停顿时间-XX:MaxGCPauseMillis,默认值200毫秒,优先处理回收价值收益最大的那些Region
化整为零,把堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,动态扮演新生代的Eden空间、Survivor空间,或者老年代空间,采用不同的策略去处理
有2048个,默认每个Region为2M
单次回收的最小单元,避免全区域的垃圾收集
-XX:G1HeapRegionSize:
设置每个Region的大小(1MB<xx<32MB && 2的N次幂)
Humongous区域:
存大小超过了一个Region容量一半的大对象
连续Humongous Region作为老年代的一部分
问题:
跨Region引用,每个Region都维护有自己的记忆集哈希表存储结构,双向,Key是别Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号
与CMS一样写后屏障来更新维护卡表,但用的是原始快照算法,需要使用写前屏障来跟踪并发时的指针变化
CMS的卡表全局一份,只需要处理老年代到新生代的引用,但old GC时,要扫描真个新生代
并发标记阶段通过原始快照(SATB)算法
解决并发标记阶段用户线程改变对象引用关系
每个Region设计两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上,默认它们是存活的,不纳入回收范围,会出现ConcurrentMode Failure问题
分为以下四个步骤:
-
1.初始标记(Initial Marking)
-
只是标记GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。需要停顿线程,耗时短
-
2.并发标记(Concurrent Marking)
-
从GC Roots的直接关联对象遍历整个对象图,耗时较长不需要停顿用户线程
-
3.最终标记(Final Marking)
-
重新处理在并发时有引用变动的对象,需要停顿线程,耗时短
-
4.筛选回收(Live Data Counting and Evacuation)
-
更新Region的统计数据,对回收价值和成本进行排序,根据用户所期望的停顿时间,
-
-XX:MaxGCPauseMillis
-
自由选择多个Region构成回收集,
-
然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间,暂停用户线程
三个阶段需Stop TheWorld
收集的速度能跟得上对象分配的速度设计导向
缺点:
1.更多的内存占用
多份卡表
2.额外执行负载
写前屏障追踪指针变化,写后屏障更新
CMS和G1之前全部收集器,所有步骤都需Stop TheWorld
CMS和G1分别使用增量更新和原始快照,实现标记阶段的并发
CMS使用标记-清除算法,避免了回收阶段收集器带来的停顿,但碎片淤积,总要StopTheWorld
CMS使用标记-复制算法,解决了碎片问题,但要Stop TheWorld
8.Shenandoah
任何堆内存大小下,收集停顿时间限制在十毫秒以内,并发对象清理整理动作。与G1堆内存布局,在初始标记、并发标记等许多阶段的处理高度一致
1.支持并发的整理回收,G1只能多线程并行 回收不能与用户线程并发
2.用名为连接矩阵的全局数据结构记录跨Region的引用,摒弃了耗费大量内存和计算资源维护的记忆集,连接矩阵类似两维坐标结构
3.Region默认不使用分代收集
分为以下九个阶段
1.初始标记(Initial Marking)
与G1一样
2.并发标记(Concurrent Marking)
与G1一样
3.最终标记(Final Marking)
与G1一样,处理剩余的SATB,并统计出回收价值最高的Region,将这些Region构成一组回收集Collection Set,短暂停顿
4.并发清理(Concurrent Cleanup)
清理整个区域内一个存活对象都没有找到的Region
5.并发回收(Concurrent Evacuation)
把回收集里的存活对象复制一份到其他未被使用的Region中,为移动对象同时,用户线程仍然可并发对被移动的对象读写访问,通过读屏障和被称为Brooks Pointers的转发指针来解决
6.初始引用更新(Initial Update Reference)
引用更新:并发回收阶段复制对象结束后,把堆中指向旧对象的引用修正到复制后的新地址
这个初始阶段只是并发回收的线程在复制对象结束后的集合点,短暂停用户线程
7.并发引用更新(Concurrent Update Reference)
按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为新值,可与用户线程一起并发
8.最终引用更新(Final Update Reference)
修正存在于GC Roots中的引用,暂停用户线程,时间与GC Roots的数量相关
9.并发清理(Concurrent Cleanup)
经过并发回收和引用更新,回收集中Region已再无存活对象,一次并发清理过程来回收这些Region的内存空间
解决用户线程对移动对象定位访问问题:
1.被移动对象原有的内存上设置保护陷阱,预设好异常处理器,由其中代码逻辑把访问转发到复制后的新对象上,需要操作系统层面的直接支持,否则用户态频繁切换到核心态,代价大
2.转发指针
类似句柄定位,在对象头前面增加一个新的引用字段,默认该引用指向对象自己,新的副本时通过CAS,字段更改为指向新对象,
读屏障:基于引用访问屏障
只拦截对象中数据类型为引用类型的读写操作
缺点:
高运行负担使得吞吐量下降
吞吐量 Parallel Scavenge>G1>Shenandoah
优点:
低延迟时间
9.ZGC收集器
基于Region/Page或者ZPage 内存布局的,暂时不设分代的,使用了读屏障、染色指针和内存多重映射等技术实现可并发的标记-整理算法,以低延迟为首要目标
ZGC的Region具有动态性——动态创建和销毁,以及动态的区域容量大小。
具有大、中、小三类容量
1.小型Region(Small Region):
容量固定为2MB,用于放置小于256KB的小对象。
2.中型Region(Medium Region):
容量固定为32MB,用于放置大于等于256KB但小于4MB的对象
3.大型Region(Large Region)
容量不固定,可动态变化,为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只存一个大对象,即它的实际容量有可能小于中型Region,最小容量可低至4MB。
大型Region在ZGC的实现中是不会被重分配,因为复制一个大对象的代价非常高昂
并发整理算法实现:
Serial收集器把标记直接记录在对象头上
G1、Shenandoah把标记记录在与对象相互独立的数据结构上,用一种相当于堆内存的1/64大小的,称为BitMap的结构来记录标记信息
染色指针把标记直接记录在引用对象的指针上
原有指针高4位提取出来存储四个标志信息
指针存有
其引用对象的三色标记状态、
是否进入了重分配集(即被移动过)、
是否只能通过finalize()方法才能被访问到
问题:
指针 = 内存地址
处理器不会管指令流中的指针哪部分存的是标志位,哪部分才是真正的寻址地址,默认都是内存地址对待,造成寻址不到
多重映射下的寻址
把染色指针中的标志位看作地址的分段符,将这些不同的地址段都映射到同一个物理内存空间
优点:
1.染色指针使得某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉
不必等待堆中所有指向该Region的引用都被修正后才能清理
2.染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量
3.染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记重定位过程相关的数据
缺点
ZGC管理的内存不可以超过4TB
不支持32位平台及window
不支持压缩指针
不支持分代,新对象回收不及时,对象分配速率低
分为以下四个大阶段
1.并发标记(Concurrent Mark)
与G1、Shenandoah一样,前后也要经过初始标记、最终标记(尽管ZGC中的名字不叫这些)的短暂停顿,不同的是会更新染色指针中的Marked 0、Marked 1标志位
2.并发预备重分配(Concurrent Prepare for Relocate)
统计出本次收集过程要清理哪些Region,将这些Region组成重分配集Relocation Set,与G1收集器的回收集(Collection Set)收益优先的增量回收不同,不做收益筛选,省去G1中记忆集的维护成本
JDK 12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段中完成的
3.并发重分配(Concurrent Relocate)
把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表,引用指针自愈:
用户线程并发访问---引用染色指针标记进入了重分配集(读屏障)--Region中转发表--新Region中的对象--更新该引用的值,使其直接指向新对象
比Shenandoah每次转发快,对用户程序的运行时负载更低
重分配集中某个Region的存活对象都复制完毕后,就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉,哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的
4.并发重映射(Concurrent Remap)
修正整个堆中指向重分配集中旧对象的所有引用,有自愈效果,不迫切,主要目的是为了不变慢,还有清理结束后可以释放转发表,与下一次垃圾收集循环中的并发标记阶段合并
NUMA内存分配:
优先尝试在请求线程当前所处的处理器的本地内存上分配对象,以保证高效内存访问,之前
Parallel Scavenge支持
吞吐量:
ParallelScavenge>ZGC>G1>Shenandoah
10.Epsilon收集器
不能够进行垃圾收集,只管理和分配对象,运行负载极小,使用场景:保证在堆耗尽之前就会退出
总结:
上述用图文的方式简单而不失重点解释面试官:说下你了解的Java收集器你该如何解答,为你在Java的第一个门槛顺利的踏入。
------------恢复内容开始------------
在两个收集器之间存在连线,可以搭配使用
收集器所处的区域,表示它是属于新生代还是老年代收集器
不存在“万能”的收集器,选择的只是对具体应用最合适的收集器
收集器发展目标:
为消除或者降低用户线程因垃圾收集而导致停顿的时间
三项指标:
1.内存占用(Footprint)
2.2.吞吐量(Throughput)
3.3.延迟(Latency)
并行(Parallel):多条垃圾收集器线程同时运行,通常默认此时用户线程是处于等待状态
并发(Concurrent):收集器线程与用户线程同时运行,应用程序的处理的吞吐量受到一定影响
1.Serial收集器
JDK1.3.1之前,HotSpot虚拟机新生代收集器的唯一选择,单线程工作,进行垃圾收集时,必须暂停其他所有工作线程,直到收集结束
优点是:
高效而简单,额外内存消耗最小,单核处理器或处理器核心数较少的环境收集效率高,没有线程交互的开销,专心做垃圾收集
Serial/Serial Old
2.ParNew收集器
Serial收集器的多线程并行版本,JDK 7之前首选的新生代收集器,除Serial收集器外,只有它能与CMS收集器配合工作
-XX:+UseConcMarkSweepGC:
激活CMS后的默认新生代收集器
-XX:+/-UseParNewGC:
禁用或强制指定
jdk8声明废弃ParNew加Serial Old以及Serial加CMS这两组收集组合,jdk9完全取消,只能ParNew和CMS
单核处理器环境,效果比不上Serial收集器
多核高效率,
-XX:ParallelGCThreads:
限制垃圾收集的线程数
3.Parallel Scavenge
基于标记-复制算法的新生代收集器,多线程并行收集
与其他收集器尽可能地缩短垃圾收集时用户线程的停顿时间不同,关注点是达到一个可控制的吞吐量
两个参数用于精确控制吞吐量:
-XX:MaxGCPauseMillis:
最大垃圾收集停顿时间(停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的,会导致GC频繁)
-XX:GCTimeRatio:
设置吞吐量大小(用户代码时间与GC运行时间的倍数)
-XX:+UseAdaptiveSizePolicy:
动态调整其他参数以提供最合适的停顿时间或者最大的吞吐量(自适应的调节策略)
4.Serial Old/PS MarkSweep
Serial收集器的老年代版本,单线程,使用标记-整理算法
JDK 5以及之前与Parallel Scavenge搭配使用
作为CMS收集器发生失败时的后备预案,在并发收集发生ConcurrentMode Failure时使用
5.Parallel Old
Parallel Scavenge的老年代版本,支持多线程并发收集,基于标记-整理算法实现
JDK 6时才开始提供,Parallel Scavenge加Parallel Old搭配组合,吞吐量多线程并发
Parallel Scavenge/Parallel Old
6.CMS收集器
并发收集、低停顿
垃圾收集线程与用户线程(基本上)同时工作,基于标记-清除算法,无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作,
关注点:最短回收停顿时间
运作过程分为四个步骤:
1)初始标记(CMS initial mark)
标记GC Roots能直接关联到的对象,速度很快,需要 Stop TheWorld
2)并发标记(CMS concurrent mark)
从GC Roots的直接关联对象遍历整个对象图,这个过程耗时较长但不需要停顿用户线程,
可以与垃圾收集线程一起并发运行
3)重新标记(CMS remark)
修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(增量更新),停顿时间比初始标记阶段稍长,远比并发标记阶段的时间短,需要 Stop TheWorld
4)并发清除(CMS concurrent sweep)
清理删除掉标记阶段判断的已经死亡的对象,耗时较长,由于不需要移动存活对象,可以与用户线程同时并发的
耗时较长的阶段都可以与用户线程一起工作
缺点:
1.基于“标记-清除”算法实现,收集结束时会有大量空间碎片产生,大对象无法分配连续空间,触发Full GC
-XX:+UseCMS-CompactAtFullCollection: JDK 9开始废弃
收集器不得不进行Full GC时开启内存碎片整理过程,须移动存活对象,(在Shenandoah和ZGC出现前)无法并发,停顿时间变长
-XX:CMSFullGCsBefore-Compaction:
JDK 9开始废弃
CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入FullGC前会先进行碎片整理,默认值为0,表示每次进入Full GC时都进行碎片整理
2.无法处理浮动垃圾,可能出现“Con-current Mode Failure”失败而导致另一次完全Stop The World 的Full GC的产生,由于在垃圾收集阶段用户线程可以并发运行,须预留一部分空间供并发收集时的分配新对象(含有新垃圾即浮动垃圾,无法当前收集处理)
-XX:CMSInitiatingOccu-pancyFraction:
当老年代使用了多少占比的空间后就激活收集,越高,内存回收频率越低,但预留的内存无法满足分配新对象,会出现并发失败Concurrent Mode Failure
虚拟机启动后备预案:
冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,停顿时间变长
Concurrent Mark Sweep
7.Garbage First G1
面向局部收集的设计思路和基于Region的内存布局形式。CMS替代者
JDK 9宣告取代Parallel Scavenge加Parallel Old组合,CMS声明不推荐使用
Mixed GC模式:
衡量标准是哪块内存中存放的垃圾数量最多,回收收益最大,不再是它属于哪个分代
回收收益:
维护一个优先级列表,跟踪各个Region回收所获得的空间大小以及回收所需时间的经验值根据用户设定允许的收集停顿时间-XX:MaxGCPauseMillis,默认值200毫秒,优先处理回收价值收益最大的那些Region
化整为零,把堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,动态扮演新生代的Eden空间、Survivor空间,或者老年代空间,采用不同的策略去处理
有2048个,默认每个Region为2M
单次回收的最小单元,避免全区域的垃圾收集
-XX:G1HeapRegionSize:
设置每个Region的大小(1MB<xx<32MB && 2的N次幂)
Humongous区域:
存大小超过了一个Region容量一半的大对象
连续Humongous Region作为老年代的一部分
问题:
跨Region引用,每个Region都维护有自己的记忆集哈希表存储结构,双向,Key是别Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号
与CMS一样写后屏障来更新维护卡表,但用的是原始快照算法,需要使用写前屏障来跟踪并发时的指针变化
CMS的卡表全局一份,只需要处理老年代到新生代的引用,但old GC时,要扫描真个新生代
并发标记阶段通过原始快照(SATB)算法
解决并发标记阶段用户线程改变对象引用关系
每个Region设计两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上,默认它们是存活的,不纳入回收范围,会出现ConcurrentMode Failure问题
分为以下四个步骤:
-
1.初始标记(Initial Marking)
-
只是标记GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。需要停顿线程,耗时短
-
2.并发标记(Concurrent Marking)
-
从GC Roots的直接关联对象遍历整个对象图,耗时较长不需要停顿用户线程
-
3.最终标记(Final Marking)
-
重新处理在并发时有引用变动的对象,需要停顿线程,耗时短
-
4.筛选回收(Live Data Counting and Evacuation)
-
更新Region的统计数据,对回收价值和成本进行排序,根据用户所期望的停顿时间,
-
-XX:MaxGCPauseMillis
-
自由选择多个Region构成回收集,
-
然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间,暂停用户线程
三个阶段需Stop TheWorld
收集的速度能跟得上对象分配的速度设计导向
缺点:
1.更多的内存占用
多份卡表
2.额外执行负载
写前屏障追踪指针变化,写后屏障更新
CMS和G1之前全部收集器,所有步骤都需Stop TheWorld
CMS和G1分别使用增量更新和原始快照,实现标记阶段的并发
CMS使用标记-清除算法,避免了回收阶段收集器带来的停顿,但碎片淤积,总要StopTheWorld
CMS使用标记-复制算法,解决了碎片问题,但要Stop TheWorld
8.Shenandoah
任何堆内存大小下,收集停顿时间限制在十毫秒以内,并发对象清理整理动作。与G1堆内存布局,在初始标记、并发标记等许多阶段的处理高度一致
1.支持并发的整理回收,G1只能多线程并行 回收不能与用户线程并发
2.用名为连接矩阵的全局数据结构记录跨Region的引用,摒弃了耗费大量内存和计算资源维护的记忆集,连接矩阵类似两维坐标结构
3.Region默认不使用分代收集
分为以下九个阶段
1.初始标记(Initial Marking)
与G1一样
2.并发标记(Concurrent Marking)
与G1一样
3.最终标记(Final Marking)
与G1一样,处理剩余的SATB,并统计出回收价值最高的Region,将这些Region构成一组回收集Collection Set,短暂停顿
4.并发清理(Concurrent Cleanup)
清理整个区域内一个存活对象都没有找到的Region
5.并发回收(Concurrent Evacuation)
把回收集里的存活对象复制一份到其他未被使用的Region中,为移动对象同时,用户线程仍然可并发对被移动的对象读写访问,通过读屏障和被称为Brooks Pointers的转发指针来解决
6.初始引用更新(Initial Update Reference)
引用更新:并发回收阶段复制对象结束后,把堆中指向旧对象的引用修正到复制后的新地址
这个初始阶段只是并发回收的线程在复制对象结束后的集合点,短暂停用户线程
7.并发引用更新(Concurrent Update Reference)
按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为新值,可与用户线程一起并发
8.最终引用更新(Final Update Reference)
修正存在于GC Roots中的引用,暂停用户线程,时间与GC Roots的数量相关
9.并发清理(Concurrent Cleanup)
经过并发回收和引用更新,回收集中Region已再无存活对象,一次并发清理过程来回收这些Region的内存空间
解决用户线程对移动对象定位访问问题:
1.被移动对象原有的内存上设置保护陷阱,预设好异常处理器,由其中代码逻辑把访问转发到复制后的新对象上,需要操作系统层面的直接支持,否则用户态频繁切换到核心态,代价大
2.转发指针
类似句柄定位,在对象头前面增加一个新的引用字段,默认该引用指向对象自己,新的副本时通过CAS,字段更改为指向新对象,
读屏障:基于引用访问屏障
只拦截对象中数据类型为引用类型的读写操作
缺点:
高运行负担使得吞吐量下降
吞吐量 Parallel Scavenge>G1>Shenandoah
优点:
低延迟时间
9.ZGC收集器
基于Region/Page或者ZPage 内存布局的,暂时不设分代的,使用了读屏障、染色指针和内存多重映射等技术实现可并发的标记-整理算法,以低延迟为首要目标
ZGC的Region具有动态性——动态创建和销毁,以及动态的区域容量大小。
具有大、中、小三类容量
1.小型Region(Small Region):
容量固定为2MB,用于放置小于256KB的小对象。
2.中型Region(Medium Region):
容量固定为32MB,用于放置大于等于256KB但小于4MB的对象
3.大型Region(Large Region)
容量不固定,可动态变化,为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只存一个大对象,即它的实际容量有可能小于中型Region,最小容量可低至4MB。
大型Region在ZGC的实现中是不会被重分配,因为复制一个大对象的代价非常高昂
并发整理算法实现:
Serial收集器把标记直接记录在对象头上
G1、Shenandoah把标记记录在与对象相互独立的数据结构上,用一种相当于堆内存的1/64大小的,称为BitMap的结构来记录标记信息
染色指针把标记直接记录在引用对象的指针上
原有指针高4位提取出来存储四个标志信息
指针存有
其引用对象的三色标记状态、
是否进入了重分配集(即被移动过)、
是否只能通过finalize()方法才能被访问到
问题:
指针 = 内存地址
处理器不会管指令流中的指针哪部分存的是标志位,哪部分才是真正的寻址地址,默认都是内存地址对待,造成寻址不到
多重映射下的寻址
把染色指针中的标志位看作地址的分段符,将这些不同的地址段都映射到同一个物理内存空间
优点:
1.染色指针使得某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉
不必等待堆中所有指向该Region的引用都被修正后才能清理
2.染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量
3.染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记重定位过程相关的数据
缺点
ZGC管理的内存不可以超过4TB
不支持32位平台及window
不支持压缩指针
不支持分代,新对象回收不及时,对象分配速率低
分为以下四个大阶段
1.并发标记(Concurrent Mark)
与G1、Shenandoah一样,前后也要经过初始标记、最终标记(尽管ZGC中的名字不叫这些)的短暂停顿,不同的是会更新染色指针中的Marked 0、Marked 1标志位
2.并发预备重分配(Concurrent Prepare for Relocate)
统计出本次收集过程要清理哪些Region,将这些Region组成重分配集Relocation Set,与G1收集器的回收集(Collection Set)收益优先的增量回收不同,不做收益筛选,省去G1中记忆集的维护成本
JDK 12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段中完成的
3.并发重分配(Concurrent Relocate)
把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表,引用指针自愈:
用户线程并发访问---引用染色指针标记进入了重分配集(读屏障)--Region中转发表--新Region中的对象--更新该引用的值,使其直接指向新对象
比Shenandoah每次转发快,对用户程序的运行时负载更低
重分配集中某个Region的存活对象都复制完毕后,就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉,哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的
4.并发重映射(Concurrent Remap)
修正整个堆中指向重分配集中旧对象的所有引用,有自愈效果,不迫切,主要目的是为了不变慢,还有清理结束后可以释放转发表,与下一次垃圾收集循环中的并发标记阶段合并
NUMA内存分配:
优先尝试在请求线程当前所处的处理器的本地内存上分配对象,以保证高效内存访问,之前
Parallel Scavenge支持
吞吐量:
ParallelScavenge>ZGC>G1>Shenandoah
10.Epsilon收集器
不能够进行垃圾收集,只管理和分配对象,运行负载极小,使用场景:保证在堆耗尽之前就会退出
总结:
上述用图文的方式简单而不失重点解释面试官:说下你了解的Java收集器你该如何解答,为你在Java的第一个门槛顺利的踏入。
------------恢复内容结束------------