一、判断垃圾算法
1、引用计数法
给每个对象添加一个计数器,当有地方引用该对象时计数器加1,当引用失效时计数器减1。用对象计数器是否为0来判断对象是否可被回收。缺点:无法解决循环引用的问题
2、可达性分析法
通过GC ROOT的对象作为搜索起始点,通过引用向下搜索,所走过的路径称为引用链。通过对象是否有到达引用链的路径来判断对象是否可被回收(可作为GC ROOT的对象:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象)
二、垃圾回收算法
1、标记-清除(Mark-Sweep)
- 标记需要回收的对象
- 清理掉要回收的对象
2、标记-整理(Mark-Compact)
- 标记需要回收的对象
- 把所有的存活对象压缩到内存一端
- 清理掉边界外的所有空间
- 避免内存随便产生
3、复制(Copy)
- 把内存分为两块,每次只是用一块
- 将正在使用的内存中的存活对象复制到未使用的内存中去,然后清除掉正在使用的内存中的所有对象
- 交换两个内存的角色,等待下次回收
三种算法对比
回收算法 | 优点 | 缺点 |
---|---|---|
标记-清除 | 实现简单 | 存在内存碎片、分配内存速度会受影响 |
标记-整理 | 无碎片 | 整理存在开销 |
复制 | 性能好、无碎片 | 内存利用率低 |
4、分代收集算法
- 把内存分成多个区域,不同区域使用不同的回收算法回收对象
- 根据对象的存活周期,把内存分成多个区域,不同区域使用不同的回收算法回收对象
优点
- 更有效的清除不在需要的对象
- 提升了垃圾回收的效率
调优原则 - 合理设置Survivor区域的大小,避免内存浪费
- 让GC尽量发生在新生代,尽量减少Full GC的发生
5、增量算法
- 每次只收集一小片区域的内存空间的垃圾
三、垃圾分布和触发条件
1、 堆内存结构
- 新对象一般分配到Eden区
- 对象大于 -XX:PretenureSizeThreshold,就会直接分配到老年代
- 新生代空间不够的时候也会分配到老年代
- 对象不一定要到达到年龄才会进入老年代
- 动态年龄:如果Survivor空间中所有相同年龄对象大小的总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象就可以直接进入老年代
2、回收类型
- 新生代回收(Minor GC | Young GC)
- 老年代回收(Major GC)
- 清理整个堆(Full GC)
- Major GC ≈ Full GC
3、触发垃圾回收的条件
3.1、新生代
- Eden区空间不足
3.2、老年代
- 老年代空间不足
- 元空间不足
- 要晋升到老年代的对象所占用的空间大于老年代的剩余空间
- 显示调用System.gc()
- 建议垃圾回收期执行垃圾回收
- -XX:+DisableExplicitGC 参数,忽略掉System.gc()的调用
四、垃圾收集器
- 垃圾收集算法:为实现垃圾回收提供理论支持
- 垃圾收集器:利用垃圾收集算法,实现垃圾回收的具体实现
- 术语-Stop The World
- 简写为STW,也叫全局停顿,Java代码停止运行,native代码继续运行,但不能于jvm进行交互
- 原因:多半由于垃圾回收导致;也可能由Dump线程、死锁检查、Dump堆等导致
- 危害:服务停止、没有响应;主从切换、危害生产环境
- 术语-并行收集 vs 并发收集
- 并行收集:指多个垃圾收集线程并行工作,但是收集的过程中,用户线程(业务线程)还是处于等待状态的
- 并发收集:之用户线程与垃圾收集线程同时工作
1、新生代收集器
1.1、Serial 收集器
- 最基本的、发展历史最悠久的收集器
- 复制算法
特点 - 单线程
- 简单、高效
- 收集过程全程Stop The World
适用场景
- 客户端程序,应用以-client模式运行时,默认使用的就是Serial
- 单核机器
1.2、 ParNew收集器
- Serial收集器的多线程版,除使用了多线程以外,其他和Serial收集器一样,包括:JVM参数、Stop The World 的表现、垃圾收集算法都是一样的
特点
- 多线程
- 可使用-XX:ParallelGCThreads设置垃圾收集的线程数
适用场景
- 主要用来和CMS收集器配合使用
1.3、Parallel Scavenge收集器
- 也叫吞吐量优先收集器
- 采用的也是复制算法
- 也是并行的多线程收集器,这一点和ParNew类似
特点 - 可以达到一个可控制的吞吐量
- -XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间(尽力)
- -XX:GCTimeRatio 设置吞吐量的大小,取值0-100,系统花费不超过1/(1+n)的时间用于垃圾收集
- 自适用GC策略:可用-XX:+UseAdptiveSizePolicy打开
- 打开自适应策略后,无需手动设置新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)等参数
- 虚拟机会自动根据系统的运行状况收集性能监控信息,动态地调整这些参数,从而达到最优的停顿时间以及最高的吞吐量
适用场景
- 注重吞吐量的场景
2、老年代收集器
2.1、Serial Old 收集器
- Serial 收集器的老年代版
- 标记-整理算法
适用场景 - 可以和Serial/ParNew/Parallel Scavenge这三个新生代的垃圾收集器配合适用
- CMS收集器出现故障的时候,会用Serial Old最为后备
2.2、Parallel Old收集器
- Parallel Scavenge 收集器的老年代版本
- 标记-整理算法
适用场景 - 关注吞吐量的场景
2.3、CMS收集器
- CMS:Concurrent Mark Sweep
- 并发收集器
- 标记-清除算法
执行过程
- 初始标记(initial mark)
-标记GC Roots能直接关联到的对象
-Stop The World - 并发标记(concurrent mark)
-找出所有的GC Roots 能关联到的对象
-并发执行,无Stop The World - 并发预清理(concurrent-preclean)
-重新标记那些在并发标记阶段,引用被更新的对象,从而减少后面重新标记阶段的工作量
-并发执行,无Stop The World
-可适用-XX:-CMSPrecleaningEnabled 关闭并发预清理阶段,默认打开 - 并发可中止的预清理阶段(concurrent-abortable-preclean)
-和并发预清理做的事情一样,并发执行,无Stop The World
-当Eden的适用量大于CMSScheduleRemarkEdenSizeThreshold的阈值(默认2M)时,才会执行该阶段
-主要作用:允许我们能够控制预清理阶段的结束时机。比如扫描多长时间(CMSMaxAbortablePrecleanTime,默认5秒)或者Eden区使用占比达到一定阈值(CMSScheduleRemarkEdenPenetration,默认50%)就结束本阶段 - 重新标记(remark)
-修正并发标记期间,因为用户程序继续运行,导致标记发生变动的那些对象
-一般来说,重新标记花费的时间会比初始标记阶段长一些,但比并发标记时间短
-存在Stop The World - 并发清除(concurrent sweep)
-基于标记结果,清除掉要清除前面标记出来的垃圾
-并发执行,无Stop The World
(为什么不是并发整理呢?用户线程还在运行) - 并发重置(concurrent reset)
-清理本次CMS GC 的上下文信息,为下一次GC做准备
优点
- Stop The World 的时间比较短
- 大多过程并发执行
缺点
- CPU资源比较敏感,并发阶段可能导致应用吞吐量的降低
- 无法处理浮动垃圾
- 不能等到老年代几乎满了才开始收集
-预留的内存不够 -> Concurrent Mode Failure -> Serial Old 作为后备
-可使用CMSInitiatingOccupancyFraction 设置老年代占比达到多少就触发垃圾收集,默认68% - 内存碎片
*标记-清除导致碎片的产生
*UseCMSCompactAtFullCollection,在完成Full GC后是否要进行内存碎片整理,默认开启
*CMSFullGCsBeforeCompaction,进行几次Full GC后就进行一次内存碎片整理,默认0
适用场景
- 希望系统听读那时间短,响应速度快的场景,比如各种服务器,应用程序
总结
- 初始标记(STW)
- 并发标记
- 并发预清理
- 并发可中止预清理
- 重新标记(STW)
- 并发清除
- 并发重置
2.4、G1收集器
- Garbge First
- 面向服务器端应用的垃圾收集器
Region - 通过参数-XX:G1HeapRegionSize 指定Region的大小
- 取值范围为1Mb ~ 32 Mb,应为2的N次幂
设计思想
- 内存分块(Region)
- 跟踪每个Region里面的垃圾堆积的价值大小
- 构建一个优先列表,根据允许的收集时间,优先回收价值高的Region
垃圾收集机制
- Young GC
- 所有Eden Region 都满了的时候,就会触发Young GC
- Eden 里面的对象会转移到Survivor Region里面去
- 原先Survivor Region 中的对象转移到新的Survivor Region中,或者晋升到Old Region
- 空闲Region 会被放入空闲列表中,等待下次被适用
- Mixed GC
- 老年代大小占整个堆的百分比达到一定阈值(-XX:InitiationgHeapOccupancyPercent指定,默认45%),就触发
- Mixed GC会回收所有Young Region,同时回收部分Old Region
Mixed GC执行过程
- 初始标记(Initial Marking)
- 标记GC Roots 能直接关联到的对象,和CMS类似
- 存在 Stop The World
- 并发标记(Concurrent Marking)
- 同CMS的并发标记
- 并发执行,没有Stop The World
- 最终标记(Final Marking)
- 修正在并发标记期间引起的变动
- 存在Stop The World
- 筛选回收(Live Data Countion and Evacuation)
- 对各个Region的回收价值和成本进行排序
- 根据用户所期望的停顿时间(MaxGCPauseMillis)来制定回收计划,并 选择一些Region回收
- 回收过程
- 选择一系列Region构成一个回收集
- 把决定回收的Region中的存活对象复制到空的Region中
- 删除掉需要回收的Region -> 无内存碎片
- 存在Stop The World
- Full GC
- 复制对象内存不够,或者无法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发Full GC
- Full GC模式下,使用 Serial Old 模式
- G1优化原则:尽量 减少Full GC的发生
*增加预留内存(增大 -XX:G1ReservePercent,默认为堆的10%)
*更早地回收垃圾(减少 -XX:InitiatingHeapOccupancyPercent,老年代达到该值就触发Mixed GC,默认45%)
*增加并发阶段使用的线程数(增大 -XX:ConcGCThreads)
特点
- 可以作用在整个堆
- 可控的停顿(MaxGCPauseMillis=200)
- 无内存碎片
适用场景
- 占用内存较大的应用(6G以上)
- 替换CMS垃圾收集器
总结
- 内存分Region
- 包括Young GC、Mixed GC、Full GC
- Mixed GC 步骤和CMS有类似之处,但也有很多差异
G1 or CMS ?
- 对于JDK 8:都可以使用。如果内存<=6G,建议使用CMS,反之使用G1
- 如果高于JDK 8:用G1,CMS从JDK9已经被废弃了
五、调优
参数 | 作用 | 参考值 |
---|---|---|
-XX:NewRatio=n | 老年代:新生代内存大小比值 | 2 |
-XX:SurvivorRatio=n | Eden区:Survivor区内存大小比值 | 8 |
-XX:PretenureSizeThreshold=n | 对象大于该值就在老年代分配,0标识不做限制 | 15 |
-Xms | 最小堆内存 | - |
-Xmx | 最大堆内存 | - |
-Xmn | 新生代大小 | - |
-XX:+DisableExpilcitGC | 忽略掉System.gc()的调用 | 启用 |