title | date | categories | tags | description | ||
---|---|---|---|---|---|---|
垃圾回收器 |
202 0-03-16 |
|
|
使用各种垃圾回收算法和垃圾回收机制,实现垃圾回收功能
分三类:
- 串行垃圾回收器
- 吞吐量优先的垃圾回收器
- 响应时间优先的垃圾回收器
- 单线程的垃圾回收器.
当进行垃圾回收的时候, 其他线程都先暂停(这个是 Stop The World. 不管什么垃圾回收器都要 Stop The World, 因为在垃圾回收的过程中, 对象的地址可能发生改变). 然后启动一个单线程, 进行垃圾回收
- 适用于 堆内存较小,CPU 核心数少的.
-
多线程
-
适用于堆内存较大, 并且需要多核 CPU 来支持.
如果只有单核 CPU, 那即使开多个线程进行垃圾回收, 也是多个线程争抢单 CPU 的时间片. 效率可能还低于单线程.
单位时间内, STW 的时间更短.
-
多线程
-
适用于堆内存较大, 并且需要多核 CPU 来支持.
垃圾回收时候, 尽可能让单次 STW 更短
吞吐量有限 和 响应时间优先区别:
比如一小时内, 响应时间优先的垃圾回收器 , 发生了 5 次垃圾回收, 每次都是 0.1 秒 (尽可能让单次 STW 更短) 吞吐量优先的垃圾回收器, 单次垃圾回收时间要 0.2 秒, 但是 1 小时内, 它只发生 2 次 GC. 它的总共花费时间为 0.4 秒,总时间上花费时间少, 吞吐量就大.
吞吐量优先 就是 垃圾回收的时间 占总共程序运行时间的占比. 占比越低, 吞吐量越高.
总结来说, 就是 响应时间优先是单次时间短, 吞吐量优先 是总时间短.
-XX:UseSericalGC
-XX:UseSericalGC = Serial + SerialOld 这两个配合使用
这个参数指定了 新生代的垃圾回收器 和 老年代的垃圾回收器 都要采用串行的.
新生代的垃圾回收算法为:复制算法 老年代的垃圾回收算法为:标记整理
回收过程:
- 4 核 CPU. 运行过程中, 内存不够, 触发垃圾回收. 让所有用户线程在安全点暂停(Stop The World). 因为在垃圾回收的过程中, 对象地址可能发生变化.
- 暂停后, 垃圾回收器开启一个线程来执行垃圾回收工作
- 垃圾回收工作结束后, 其他线程恢复运行
-XX:+UseParallelGC 指定新生代采用并行的垃圾回收器(复制算法)
-XX:+UseParallelOldGC 指定老年代采用并行的垃圾回收器(标记整理算法)
这两个配合使用
JDK1.8 默认开启的上面的参数的. 也就是在 JDK1.8 默认使用吞吐量优先垃圾回收器
工作流程:
- 多核心 CPU 正常运行. 内存不足了, 触发垃圾回收. 所有用户线程在安全点暂停
- 垃圾回收器开启多个线程进行垃圾回收工作. 线程个数默认=CPU 核心数
- 垃圾回收完成之后, 用户线程恢复运行
在垃圾回收过程中, CPU 占用率会飙升到 100%
-XX:ParallelGCThreads=n 并行线程数指定
-XX:GCTimeRadio=radio 可接受GC时间占比(目标吞吐量) , 也就是指垃圾回收的总时间 占 程序运行时间的占比. 这是指定一个目标. 指定了之后, JVM 会自动调整堆的大小, 来达到这个目标 计算公式是: 1/(1+radio)
默认 radio=99. 因此占比默认是 1:100
比如程序执行 100 分钟, 那么有 1 分钟的时间可以用来垃圾回收
-XX:MaxGCPauseMillis:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。默认值是 200 毫秒.MaxGCPauseMillis优先级高于GCTimeRadio
JDK 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy,会根据GC的情况自动计算计算 Eden、From 和 To 区的大小;
开启:-XX:+UseAdaptiveSizePolicy 关闭:-XX:-UseAdaptiveSizePolicy
- 在 JDK 1.8 中,如果使用 CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将 UseAdaptiveSizePolicy 设置为 false;不过不同版本的JDK存在差异;
- UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
- 由于AdaptiveSizePolicy会动态调整 Eden、Survivor 的大小,有些情况存在Survivor 被自动调为很小,比如十几MB甚至几MB的可能,这个时候YGC回收掉 Eden区后,还存活的对象进入Survivor 装不下,就会直接晋升到老年代,导致老年代占用空间逐渐增加,从而触发FULL GC,如果一次FULL GC的耗时很长(比如到达几百毫秒),那么在要求高响应的系统就是不可取的。
这个应该是 JDK8 默认的.
说 CMS, 其实说说两个, 老年代采用 CMS, 新生代采用 并行的垃圾回收器(复制算法), 配合使用.
-XX:+UseConcMarkSweepGC 与 -XX:+UseParNewGC 配合使用 指定老年代的垃圾回收器 指定新生代采用并行的垃圾回收器(复制算法)
Conc指并发的. 基于标记清除算法的垃圾回收器
并发的:指在垃圾回收的过程中, 用户线程可以继续执行.
工作流程:
- 多个 CPU 执行, 内存不足, 在老年代发生GC
- 所有用户线程在安全点暂停(STW) , 垃圾回收器进行初始标记工作
- 初始标记完了, 用户线程就可以恢复运行. 同时, 垃圾回收器进行并发标记.
- 并发标记完了, 要重新标记, 这个时候要 STW, 即所有用户线程在安全点暂停
- 完了之后, 用户线程恢复运行. 同时, 垃圾回收器会进行并发清理工作
只在初始标记和重新标记, 会造成 STW. 响应时间短.
在执行垃圾回收的时候, 对 CPU 的占用并不高.
在进行并行垃圾清理的时候, 用户并行运行, 产生的新垃圾, 称为浮动垃圾. 浮动垃圾需要在下一次垃圾清理的时候才能清理掉. 因此, 不能等内存完全不够用了再进行垃圾回收, 需要预留一些空间, 来保存浮动垃圾
通过参数: -XX:CMSInitiatingOccupancyFraction=percent 来设置内存占比.
比如老年代的内存使用达到 80%的时候, 就要开始垃圾回收了. 给浮动垃圾预留 20%的空间
老年代使用标记清除算法.
分配内存时, 新生代内存不足, 而老年代由于碎片过多, 也不足, 这样就会造成并发失败.
此时, 老年代的垃圾回收器(CMS)就无法正常工作, 它就会退化为 SerialOld, 进行一次单线程的, 串行的垃圾回收器.
SericalOld 垃圾回收器: 采用标记整理算法. 整理后, 碎片少了, 可用内存多了, 再恢复成 CMS 继续工作.
JDK9 默认的垃圾回收器, 废弃了 CMS 垃圾回收器
- 同时注重吞吐量和低延迟
- 超大堆内存, 会将堆划分为多个大小相等的 Region. 每个区域都可以独立的作为 伊甸园,幸存区,老年代 -XX:G1HeapRegionSize=size , 但是这个 size 只能设置成 1 2 4 8 16 这样的.
- 整体上是标记+整体算法, 两个区域之间是复制算法
工作流程图:
G1 将整个对区域划分为若干个大小相等的Region,每个Region的大小是2的倍数(1M,2M,4M,8M,16M,32M,通过设置堆的大小和Region数量计算得出。每个区域都可以独立的作为 伊甸园,幸存区,老年代
开始对象分配内存, 会分配在伊甸园. 如下. 空白的表示内存未被占用
当伊甸园被逐渐占满, 就会触发新生代 的 垃圾回收. 会触发 STW.
将伊甸园中存活的对象, 用复制算法, 复制到幸存区中
当幸存区的空间逐渐占满, 或者对象年龄达到阈值, 或者达到直接进入老年代的条件, 对象会晋升到老年代中
对 对象进行初始标记和并发标记
初始表示:只是标记根对象 新生代 GC 时候发生, 也就是第一个阶段 新生代回收的 STW 时候 做的. 注意: 这个不是在这一阶段做的, 是在第一阶段 STW 的时候做的.
这个阶段做的是下面的并发标记!
并发标记:从根对象出发, 顺着引用连去 标记其他对象 当老年代占用堆空间达到阈值, 会进行并发标记. 并发执行, 不会影响用户线程, 不会 STW 阈值通过参数: -XX:InitiatingHeapOccupancyPercent=percent (默认 45%)
对 Eden 幸存区 老年代 进行全面的垃圾回收
- 最终标记, 并且要 STW
- 将 Eden 的存活对象拷贝到幸存区
- 幸存区的根据年龄或者动态判定, 继续复制到幸存区, 或者晋升到老年代
- 老年代的, 根据暂停时间, 先回收一部分最不需要的对象, 下一次再回收一部分, 这样既保证了有空间可用, 也没有花费很长时间. 存活的对象, 是要复制另外的老年区中. 复制的目的是:①保留存活对象②整理内存, 较少碎片
G1 可以参考: https://www.cnblogs.com/lsgxeva/p/10231201.html
Minor GC 和 FullGC 总结
如图
四种垃圾回收器
SerialGC 串行 ParallelGC 并行 CMS 响应时间优先的垃圾收集器 G1
这四种垃圾回收器的 新生代垃圾回收, 都是 Minor GC. 没问题
当老年代空间不足, SerialGC 和 ParallelGC 垃圾回收器会触发 FullGC.
G1 不同.
当老年代占用堆空间达到阈值(45%), 会进行并发标记阶段和混合收集阶段.
如果回收速度高于用户线程产生垃圾的速度, 此时还不是 FullGC, 只算是(并发收集的阶段?)
如果回收速度赶不上用户线程产生垃圾的速度, 此时, 并发收集失败, 退化成一个SerialGC, 进行 FullGC
CMS 也差不多