分类
- 按线程
- 串行
- 并行
- 工作模式
- 并发式
- 独占式
- 碎片处理方式
- 压缩式
- 非压缩式
- 工作内存空间
- 年轻代
- 老年代
评估GC的性能指标
- 吞吐量:运行用户代码的时间占总运行时间的比例
- 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
- 内存占用: java堆区所占的内存大小
垃圾回收器
- Serial
- 回收年轻代
- 采用复制算法、串行回收和“Stop-the-world”机制的方式执行内存回收
- 优势
- 简单而高效(在单线程比),比较适合单CPU
- 使用场景
- 可用内存一般不大,可以在较短时间内完成垃圾收集,只有不频繁发生,使用串行回收器是可以接受的
- 使用-XX:+UseSerialGC 参数可以指定年轻代和老年代都使用串行收集器
- 等价于 新生代使用 Serial GC,且老年代使用Serial Old GC
- Serial Old
- 回收老年代
- 采用了串行回收和“stop the world”机制,只不过内存回收算法使用的是标记-压缩算法
- Serial Old是运行在Client模式下默认的老年代的垃圾回收器
- Serial Old在server模式下主要有两个用途
- 与新生代的Parallel Scavenge配合使用
- 作为老年代的CMS收集器的后备垃圾收集方案
- ParNew
- 回收年轻代
- 采用复制算法、串行回收和“Stop-the-world”机制的方式执行内存回收
- Parallel Scavenge
- 回收年轻代
- 采用复制算法、串行回收和“Stop-the-world”机制的方式执行内存回收
- 可控制吞吐量
- 自适应调节策略
- 搞吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。常见案例
- 批量处理
- 订单处理
- 工资支付
- 科学计算
- 参数配置
- -XX:+UseParallelGC 手动指定年轻代使用parallel并行收集器执行内存回收任务
- -XX:+UseParallelOldGC: 手动指定老年代都是使用并行回收收集器。
- 分别适用于新生代和老年代。默认jdk8是开启的
- 上面两个参数,默认开启一个,另一个也会被开启
- -XX:ParallelGCThreads 设置年轻代并行收集器的线程数。一般地,最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能
- 在默认情况下,当CPU数量小于8个,ParallelGCThreads 的值等于CPU数量
- 当CPU数量大于8个,ParallelGCThreads 的值等于 3+ [5*CPU]/8
- -XX:MaxGCpauseMillis 设置垃圾收集器最大停顿时间。毫秒
- -XX:GCTimeRatio 垃圾收集时间占总时间的比例(=1/(N+1))。用于衡量吞吐量的大小
- 默认值 99 (0,100)
- -XX:+UseAdaptiveSizePolicy 设置Parallel Scavenge 收集器具有自适应调节策略
- 在这种模式下,年轻代的大小、eden和survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点
- 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量和停顿时间,让虚拟机自己完成调优工作
- Parallel Old
- 回收老年代
- 标记压缩算法
- 并行回收
- stop-the-world机制
- CMS
- 回收老年代
- 低延迟、并发
- 标记清除算法
- stop-the-world
- 工作过程
- 初始标记
- 所有的工作线程会stw,这个阶段的主要任务仅仅是标记出GC Roots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以速度非常快
- 并发标记
- 从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
- 重新标记
- 由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行,因此为了修正并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短
- 并发清除
- 此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间
- 初始标记
- 垃圾回收时机
- 不能等到内存不足才进行回收,因为回收和用户线程是并发执行的,需要设置一定的阈值,便开始进行回收,要是CMS运行期间预留的内存无法满足程序的需求,就会出现“Concurrent Mode Failure”失败,这是jvm启动预备方案,临时使用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就长了
- 优点:
- 并发收集
- 低延迟
- 缺点
- 会产生碎片
- 对CPU资源非常敏感
- 在并发阶段,虽然不会导致用户停顿,但会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低
- 无法处理浮动垃圾
- 并发标记如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收
- 为什么要重新标记
- 一个本应该不是垃圾的对象被视为了垃圾
- 增量更新是站在新增引用的对象的角度来解决问题。所谓增量更新,就是在赋值操作之前添加一个写屏障,在写屏障中记录新增的引用。比如,用户线程要执行:A.f = F;那么在写屏障中将新增的这个引用关系记录下来。标准的描述就是,当黑色对象新增一个白色对象的引用时,就通过写屏障将这个引用关系记录下来。然后在重新标记阶段,再以这些引用关系中的黑色对象为根,再扫描一次,以此保证不会漏标。
- 常用参数
- -XX:+UseConcMarkSweepGC 手动指定使用CMS
- -XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阈值,一旦达到该阈值,便可以进行回收
- -XX:+UseCMSCompactAtFullCollection 用于指定在执行完Full GC对内存空间进行压缩整理,以此避免内存碎片的产生。
- -XX:CMSFullGCsBeforeCompaction 设置在执行多少次Full GC后对内存空间进行压缩整理
- -XX:ParallelCMSThreads 设置CMS的线程数量
- G1
- 整堆
- 特点
- 并行与并发
- 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力。此时用户线程stw
- 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用的情况
- 分代收集
- 会区分年轻代和老年代,年轻代依然有eden区和s区。但从堆的结构上看,它不要求整个eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量
- 将堆空间分为若干个区域,这些区域中包含了逻辑上的年轻代和老年代
- 兼顾年轻代和老年代
- 空间整合
- CMS 标记清除算法、内存碎片、若干此GC后进行一次碎片整理
- 内存回收时以region作为单位的。region之间是复制算法,但整体上可以看做是标记压缩算法
- 可预测的停顿时间模型
- 可以预测:使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
- 并行与并发
- 参数设置
- -XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务
- -XX:G1HeapRegionSize 设置每个region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的java堆大小划分出约2048个区域。默认是堆内存的1/2000
- -XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认是2000ms
- -XX:ParallelGCThread 设置STW工作线程数的值。最多设置为8
- -XX:ConcGCThreads 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右
- -XX:InitiatingHeapOccupancyPercent 设置触发并发GC周期的java堆占用率阈值。超过此值,就触发GC。默认是45
- 区域
- Eden
- S
- Old
- H
- 如果一个对象的容量超过了0.5的region就会放到h区,如果一个放不下,就找连续的空间存放 也是放在H区
- 回收过程
- 年轻代GC
- 老年代并发标记过程
- 混合回收
查看默认的垃圾回收器
- -XX:+PrintCommandLineFlags: 查看命令行相关参数(包含使用的垃圾收集器)
- 使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID
- jdk8 新生代采用的是Parallel Scavenge,老年代Parallel Old。
- jdk9 使用的G1