1. 评价GC垃圾收集器的标准
- 吞吐量 (用户线程执行时间/ (用户线程执行时间 + 垃圾回收线程执行时间 ))
- 暂停时间
2. 垃圾回收器历史
jdk对应垃圾收集器的版本 :
jdk6中, 默认parallel + serial old
jdk8中, 默认parallel + parallelold
jdk9中, 默认G1
jdk14中 删除cms垃圾收集器
jdk5中 推出cms垃圾收集器
3. 垃圾回收器
① Serial回收器 (串行回收)
收集年轻代, 使用复制算法
② parnew回收器 (并行回收)
手机年轻代, 采用复制算法
③ Parallel Scavenge回收器 (吞吐量优先)
采用复制算法
自适应调节策略
高吞吐量则可以高效率地利用CPU时间, 尽快完成程序的运算任务, 主要适合在后台而不需要太多交互的任务, 因此, 常在服务器环境中使用, 例如, 执行批量操作, 订单处理, 工资支付, 科学计算的应用程序
参数配置 :
-XX: ParallelGCThreads : 设置年轻代并行收集器的线程数量(最好与cpu数量相同)
-XX:+UseAdaptiveSizePolicy : 是否开启自适应调节策略
在这种模式下, 年轻代的大小, Eden和survivor的比例, 晋升老年代的对象年龄等参数会被自动调整(from : to: eden = 1:1:8 实际查看是1:1:6)
④ cms(Concurrent-Mark-Sweep)垃圾回收器 ( 并发收集器)
采用标记清除算法
cms垃圾收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间, 停顿时间越短(低延时), 就越适合与用户交互的程序, 良好的响应速度能提升用户体验
- 目前很大一部分的java应用集中在互联网站或者B/S系统的服务器上, 这类应用尤其重视服务的响应速度, 希望系统停顿时间最短,
- cms采用标记清除算法
1 . 初始标记
仅仅只是标记处GC Roots能直接关联到的对象
2. 并发标记
从gc roots直接关联的对象开始, 遍历全部对象的过程, 比较耗时, 但是不需要停顿用户线程
3. 重新标记
修正并发标记期间, 因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录
解释 : 在遍历gc roots时, 用户线程也在执行, 若此时遍历过的一个对象没有被引用,线程并发执行, 这期间可能导致遍历过的这个对象又被其他对象引用,所以才需要重新标记阶段,在遍历一遍, 看看有没有漏标记的对象
4. 并发清除
清理删除标记阶段判断已经死亡的对象, 释放内存空间
由于垃圾收集阶段, 用户线程没有中断, 所以在cms回收过程中, 还应该确保应用程序用户线程有足够的内存可用,因此, cms收集器不能像其他收集器那样, 等到老年代几乎完全填满才进行收集, 而是当堆内存使用率达到某一阈值时(jdk5 68%, jdk6之后92%),便开始垃圾回收, 以确保应用程序在cms工作过程中有足够的空间支持应用程序运行, 要是cms运行期间预留的内存无法满足程序需要, 就会出现一次"Concurrent Mode Failure"失败, 这时虚拟机将启动后备预案, 临时启用serial old 收集器来重新进行老年代的垃圾收集, 这样停顿时间就很长了.
为什么cms垃圾回收器不用标记压缩算法?
在并发清理过程中, 如果使用标记压缩, 则需要移动对象地址, 此时用户线程仍在运行
cms优点
- 并发收集
- 低延迟
cms缺点
- 会产生内存碎片
- cms对cpu资源非常敏感, 在并发阶段, 虽然不会停顿用户线程, 但是会降低系统的吞吐量
- cms无法收集浮动垃圾
在并发阶段, 由于程序的工作线程和垃圾收集线程时同时运行, 那么在并发标记阶段, 如果产生新的垃圾对象, cms将无法对这些对象进行标记, 导致新产生的垃圾对象没有被及时回收, 只能下次回收
常用参数 :
⑤ G1垃圾收集器 (实现目标 : 在可控的延迟时间内, 尽可能的提高吞吐量)
1. 并行与并发
- 并行性 : 在g1回收期间, 可以有多个gc线程同时工作, 有效利用多核计算能力,此时用户线程stw
- 并发性 : g1拥有与应用程序交替执行的能力, 部分工作可以和应用程序同时执行
2. 分代收集
- g1依然属于分代型垃圾回收器, 他会区分年轻代和老年代, 年轻代依然有eden和seuvivor,但是从堆的结构上看, 他不要求整个Eden区, 年轻代或者老年代都是连续的, 也不再坚持固定大小和固定数量
- 将堆空间分为若干个区域, 这些区域中包含了逻辑上的年轻代和老年代
- 和之前的各类垃圾收集器不同, 他兼容年轻代和老年代
3. 空间整理
- region之间是复制算法, 整体上看实际可看作标记-压缩算法
4. 可预测的停顿时间模型
G1除了追求低停顿外, 还能建立可预测的停顿时间模型, 能让使用者明确指定一个长度为M毫秒的时间片段内, 消耗在垃圾收集上的时间不操作N毫秒
- 由于分区的原因, g1可以只选取部分区域进行内存回收, 这样缩小了回收的范围, 因此对于全局停顿情况的发生能得到较好的控制
- g1跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收做需要的时间),在后台维护了一个优先列表, 每次根据允许的收集时间, 优先回收价值最大的region, 保证g1在有限的时间内可以获取尽可能高的收集效率
常用参数 :
- -XX: G1HeapRegionSize 设置每个region大小, 默认是堆空间的1/2000
- -XX: MaxGCPauseMillis 设置期望达到的最大gc停顿时间指标, 默认是200ms
G1通过每次只清理一部分而不是全部region的增量式清理, 来保证每次gc停顿时间不会过长
一个region的内部结构, 用来分配空间, 采用指针碰撞
G1垃圾回收过程
remember set
年轻代GC
jvm启动, G1准备好Eden区, 程序创建的对象都到Eden区, 当Eden区空间不足时, G1会启动一次年轻代垃圾回收
年轻代垃圾回收只会清理Eden区和Sruvivor区
YGC时, 程序会STW, G1创建回收集(Collection Set), 回收集是指需要被回收的内存分段的集合
过程 :
① 扫描根 :
根是指static变量指向的对象, 正在执行的方法中局部变量表等, 根以用连同RSet记录的外部引用作为扫描存活对象的入口
② 处理dirty card queue的card, 更新Rset, 此阶段完成后, RSet可以准确的反应老年代对所在的内存分段中对象的引用
③ 处理RSet
识别被老年代对象指向的Eden中的对象, 这些被指向的Eden中的对象被认为时存活对象
④ 复制阶段
同正常的年轻代垃圾回收
⑤ 处理引用
处理soft weak phantom等引用, 最终Eden空间的数据为空, GC停止工作
并发标记过程
① 初始标记阶段
标记从根节点直接可达的对象, 这个阶段时stw的, 并且会触发一次年轻代GC
② 根区域扫描
g1 gc扫描survivor区直接可达的老年代区域对象, 并标记被引用的对象, 这一过程必须在ygc之前完成
③ 并发标记
在整个堆中,进行并发标记(和应用程序并发执行), 此过程可能被ygc中断, 并发标记过程中, 若发现区域对象中所有对象都是垃圾,那这个区域会被立即回收,并发标记过程中同时会计算每个区域的对象活性(区域中存活对象的比例)
④ 再次标记
相当于cms的重新标记
⑤ 独占清理
计算各个区域的存活对象和GC回收比例, 并进行排序, 识别可以混合回收的区域, 为下阶段做铺垫, 是stw的
⑥ 并发清理阶段
识别并清理完全空闲的区域
混合回收
日志分析
YGC
FULL GC
举例 总空间20m, 年轻代10m (Eden : from : to = 8:1:1) 老年代10M
现有对象1 = 2m,对象2= 2m,对象3 = 2m,对象4 =4m, 对象依次被放入堆中
jdk7中, 进行完gc之后
对象1,2,3 在老年代, 对象4在年轻代
jdk8中 进行完gc之后
对象4在老年代, 1,2,3在年轻代
具体看198集