标记-复制算法
利用GC可达性分析算法找出存活的对象(蓝色部分),把存活的对象复制到右边未使用的空间,然后把原来的内存全部清空掉。
年轻代使用了复制算法,老年代未使用复制算法。是因为标记复制算法需要将一半空间空闲出来,年轻代相对老年代来说使用内存小,而老年代使用内存比较大,若空出一半,则会造成大量内存使用率不高。
标记-清除算法
将垃圾对象标记出来进行清除回收。
一般老年代使用标记清除算法
1. 效率问题 (如果需要标记的对象太多,效率不高)
2. 空间问题(标记清除后会产生大量不连续的碎片)
标记-整理算法
直接将存活的对象往垃圾对象或可用内存移动覆盖,并将地址修改为移动后的内存地址,然后将所有其他对象清除掉,使内存十分规整,并且解决了标记清除算法产生不连续的碎片问题,即可以存放一些比较大的对象。
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
垃圾收集器
1.1 Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器
适用:几十M~几百M堆内存
新生代采用复制算法,老年代采用标记-整理算法
1.2 Parallel Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))
Parallel收集器其实就是Serial收集器的多线程版本
适用:2G~3G左右堆内存
JDK8默认的新生代和老年代收集器是Parallel
新生代采用复制算法,老年代采用标记-整理算法
1.3 ParNew收集器(-XX:+UseParNewGC)
新生代采用复制算法,老年代采用标记-整理算法
ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
1.4 CMS收集器(-XX:+UseConcMarkSweepGC(old))
获取最短回收停顿时间为目标,它非常符合在注重用户体验的应用上使用
并发标记占整个垃圾收集过程的80%以上
Parallel和CMS区别
- Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)
- CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)
同样的垃圾对象,对于Parallel来说,需要STW总的时间较少,比如1S;对于CMS来说,需要STW总的来说时间较多,比如2S,但是STW被分散开来,如0.1S间隔STW一次,那么用户几乎感觉不到卡顿。对于大内存推荐使用CMS,若小内存如2~3G之间那么推荐使用Parallel
- 对CPU资源敏感(会和服务抢资源);
- 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数-XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
- 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收。也就是说,如果老年代满了的时候,回收过程中,这时用户线程又有对象到老年代,那么就会启动STW机制,使用serial old垃圾收集器来回收掉。
CMS的相关核心参数
1. -XX:+UseConcMarkSweepGC:启用cms
2. -XX:ConcGCThreads:并发的GC线程数
3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比),这个参数就是为了避免上面的concurrent mode failure情况,若系统中大对象比较多,那么须将这个值适当调小
6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
垃圾收集器组合
上面是年轻代垃圾收集器,下面是老年代垃圾收集器,连线代表可组合。
补充:G1适合用在堆内存大于8G的系统
记忆集与卡表
在新生代做GCRoots可达性扫描过程中可能会碰到跨代引用的对象,这种如果又去对老年代再去扫描效率太低了。
为此,在新生代可以引入记录集(Remember Set)的数据结构(记录从非收集区到收集区的指针集合),避免把整个
老年代加入GCRoots扫描范围。事实上并不只是新生代、 老年代之间才有跨代引用的问题, 所有涉及部分区域收集
(Partial GC) 行为的垃圾收集器, 典型的如G1、 ZGC和Shenandoah收集器, 都会面临相同的问题。
垃圾收集场景中,收集器只需通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针即可,无需了解跨代引
用指针的全部细节。
hotspot使用一种叫做“卡表”(cardtable)的方式实现记忆集,也是目前最常用的一种方式。关于卡表与记忆集的关系,cardtable和Remember Set可以类比为Java语言中HashMap与Map的关系。
卡表是使用一个字节数组实现:CARD_TABLE[ ],每个元素对应着其标识的内存区域一块特定大小的内存块,称为“卡
页”。