-
并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
-
并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上
-
吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 )),也就是。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
-
串行
单线程、内存较小,个人电脑(CPU核数较少)因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态(串行垃圾回收算法:由于是单线程的垃圾回收算法,故每次只有一个线程执行垃圾回收算法,其他线程处于阻塞的状态) -
安全点:让其他线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象
-
响应时间优先的垃圾回收
多线程,堆内存较大,多核CPU 尽可能让单次STOP THE WORLD 的时候最短,0.1 0.1 0.1 0.1 0.1 = 0.5 -
吞吐量优先的垃圾回收
JDK1.3.1之前的是HotSpot续集级新声代收集器的唯一选择
- 该收集器是单线程工作的收集器
- 单线程指的是:
- 不仅仅是用一个处理器或者一条收集器线程去完成垃圾收集工作
- 更强调的是进行垃圾收集的时候,会暂停其他所有的工作,直到收集结束
- 优点:没有线程的交互开销,可以更高效的执行垃圾收集(单线程)操作
- 新生代内存不足的时候发生的垃圾回收:minor gc
是Serial 收集器老年代版本,同样也是单线程的收集器,使用标记-整理算法。
用途:
- 客户端模式: 供HopSpot 虚拟机使用
- 服务端模式:
- jdk5 以及之前的版本 和Parall Scavenge收集器搭配使用
- 作为CMS收集器失败后的后备预案
- 老年代内存不足的时候发生的垃圾回收:full gc
- JDK1.7之前遗留的系统中首选的新生代垃圾收集器
- 实质上是Serial 收集器的多线程并发版本
- 其中收集算法、Stop the world、对象分配策略、回收策略等都与Serial收集器完全一致
Parallel Scavenge收集器(吞吐量优先的收集器)
- 优点:垃圾收集时候系统资源的高效利用
- 缺点:在单核心处理器的环境中效果要低于Serial收集器
基于标记-复制算法实现的收集器,诸多特性从表面上和ParNew类似
独特之处
- 关注的目标不同:
- CMS 等收集器的关注点是尽可能的缩短垃圾收集时用户线程的停顿时间 而Parall Scavenge收集器 关注点是达到一个可控制的吞吐量。
- 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 运行垃圾收集时间)
垃圾回收停顿的时间越短就越需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验,而高吞吐量则可以最高效率地利用处理资源。尽快完成程序的运算任务
适用场景:
在后台运算而不需要太多交互的分析任务
- 新生代内存不足的时候发生的垃圾回收:minor gc
- Parall Scavenge收集器老年代的版本,在JDK 1.6时 开始使用,也是支持多线程的并发收集。
- 基于标记-整理算法实现
- 老年代内存不足的时候发生的垃圾回收:full gc
-
是一种获取最短回收停顿时间为目的,基于标记-清除算法的收集器
-
收集过程:
- 初始标记:该过程需要Stop the world 仅仅需要标记一下GC Root 能直接关联到的对象,速度很快
- 并发标记 : 从GC Root 的直接关联对象开始遍历整个对象图的过程,整个过程耗时较长但是不需要停顿用户线程,可以并行执行
- 重新标记 : 该过程需要Stop the world ,修正并发标记期间因用户线程继续运作而导致标记变动的那一部分对象的标记记录
- 并发清除 : 清理删除标记阶段判断的意死亡的对象,由于不需要移动存活的对象,所以和用户线程并发执行
-
新生代内存不足的时候发生的垃圾回收:minor gc
-
优点:系统停顿的时间较短,有很好的用户交互体验
-
缺点:
- 并发阶段。因为占用一部分线程,导致应用程序变慢,降低总吞吐量
- 无法处理“浮动垃圾”(在CMS的并发标记和处理阶段,用户线程是还在运行的。程序在运行中也会伴随着垃圾对象的产生,但是这一部分是在标记之后产生的,CMS无法再档当次的收集中回收它们,只能等到下一次处理),有可能出现“Concurrent Mode Failure”失败而导致淋一次完全“Stop the World”的 Full GC的产生
- 收集结束后会产生大量的空间碎片
- 应用场景:java应用网站或者基于浏览器的B/S系统服务端上、
- 本质:优先收集垃圾较多的区域(实现最大的价值)
- 发展阶段
- 2004 论文发布
- 2009 JDK 6u14体验
- 2012 JDK 7u4官方支持
- 2017 JDK 9默认
- 适用场景
- 同时注重吞吐量,和低延迟
- 默认的暂停目标是200ms 超大堆内存,会将堆划分为多个大小相等的Region
- 整体上是标记 + 整理算法,两个区域直接是复制算法
- 参数设置
1. 回收阶段 阶段一:Young Collection
- 在JDK 9 之前是默认非开启的 需要手动操作 -XX:+UseG1GC
- 设置堆内存 -XX:G1HeapRegionSize=size
- 设置最大暂停时间 -XX:MaxGCPauseMillis=time
阶段二:Young Collection + CurrentMark该阶段会发生STW,将新创建的对象放到伊甸园区,当内存达到阈值,会触发Young Collection(查找Root GC,进行可达性分析),将幸存对象拷贝到幸存区,如果幸存区内存紧张,就会将达到老年代年龄的对象晋升老年代区域,而不够年龄的会重新拷贝到新的幸存区,周而复始、
- 在young colleciton时会进行GC ROOT的初始标记
- 老年代占用堆内存空间达到阈值时,进行并发标记(不会STW)由下面参数决定
-XX:InitiatingHeapOccupancyPercent=percent #(默认45%)阶段三:Mixed Collection
会对Eden(伊甸园),Suvior(幸存区),old(老年代)进行全面的回收
- 最终标记:重标记
- 拷贝存活
- 对于老年代:根据最大暂停时间,利用复制算法,优先回收垃圾多的区域(并不是所有的)
- 老年代内存不足,当垃圾的产生速度 > 垃圾收集的速度,这时就会变换串行收集 进行Full GC操作(该操作消耗更长时间)
- 老年代引用新生代的问题
新生代垃圾回收的过程:找到GC Root(根对象),可达性分析,找到存活对象,将其拷贝复制到幸存区
那么问题就来了,找到GC Root,很多都是来自于老年代,然而遍历查找所有的老年代,效率非常低,因此采用“卡表分析”法
- 将老年代的区域再次进行细分,分成一个个的卡表形式,如果其中有一个对象引用了新生代对象,那么对应的卡就会被标记为赃卡,
- 其中新生代中 Remembered Set会记录外部引用(也就是赃卡)
- 新生代垃圾回收的时候,直接通过Remembered Set 获取赃卡,直接对应的GC Root
- 在引用变更时通过 post-write barrier + dirty card queue 异步更新赃卡队列
concurrent refinement threads 更新 Remembered Set
- 优点:减少搜索范围,提高搜索效率
情况分析:
如图所示,在并发标记期间有线程对C对象的引用做了修改,把AB之间的引用断开,这时就会标记C为垃圾,将其回收
Remark标记此时就展示了其重要的作用:
只要对象发生了修改,就会执行写屏障,将其加入到队列中,并改变其引用类型,当执行到并发标记的时候,会从该队列中取出对象,查看其引用类型,如果有引用就不会被回收。
更新中,,,



