一、jdk 1.8 及其之前的分代模型:
堆内存结构必须熟悉:(垃圾回收,性能调优常用)
过程简单分析:
①年轻代和老年代在堆内存中的占比默认 1 :2
②年轻代又分为两个区,伊甸园区(Eden)和幸存区(S0和S1),分别占8:1:1
③新对象会先放到伊甸园区,当伊甸园区被对象占满时,会有执行引擎开启一个GC垃圾收集线程(利用根可达性分析算法判断是否是非垃圾对象),没有被引用的对象就是需要被回收的,然后非垃圾对象就会被移动到幸存区S0,并且寿命+1(说明经历了一次垃圾回收)。
继续重复刚刚,新对象又会放到伊甸园区,当伊甸园区再次被对象占满时,又会有执行引擎开启一 个GC垃圾收集线程,将 非垃圾对象移动到S1,同时 年龄+1,S0中的对象,如果是垃圾对象会直接被 回收,若不是垃圾对象,会被放到S1,寿命再+1,(移动用到的是标记复制算法)。
④当寿命到达15时,(经历了15次Minor GC),就将该对象移动到老年代中去。
⑤当老年代也被堆满时,就会产生老年代 GC,即Full GC。(老年代满了清理时,大部分垃圾回收器用到的是标记整理算法)。
二、常见的垃圾回收器:
(左边分代模型,用于jdk1.8及之前; 右边的分区模型,1.9及之后)
(1)串行垃圾回收器(Serial + SerialOld):
单线程的工作方式(会暂停所有用户线程)
(2)并行的垃圾回收器[高吞吐量](Parallel Scavenge + Paraller Old) 并行的垃圾回收器[PS+PO---jdk 8默认的垃圾回收器]
综上四种垃圾回收器 Serial SerialOld PS PO都会导致一个现象: 用户线程的阻塞!
用户线程暂停 <==> STW
比如:垃圾收集的时候,用户在访问的时候会直接卡主...没有线程来给用户来用.
☆☆☆(3)并发的垃圾回收器[高响应速度] ParNew(新生代) + CMS(老年代) ☆☆☆
CMS:Concurrent Mark Sweep并发标记清除
ParNew是在Parallel Scavenge基础上做的改良,配合CMS使用,但大致没什么变化!
别的老年代的Serial Old和PO都采取"标记整理",但是CMS采取"标记清除"!!
(1) 初始标记:
找到所有GC Root根对象,短暂STW.简单标记一下
(2) 并发标记:
- 根据GC Roots的直接关联对象开始进行对象图的遍历.且该阶段不会暂停用户线程,允许gc线程和用户线程并发执行
- 通过写屏障技术记录下发生错标问题的黑色对象..并放入队列中
(3) 重新标记:
会通过"增量更新"的解决方案解决上一阶段产生的错标问题.
(4) 并发清理:
清理掉确定会被回收的垃圾
- 浮动垃圾:
在gc线程与用户线程并发进行的阶段,gc线程在"并发标记"阶段,在搜集垃圾,但是在此时阶 段用户线程的操作导致的原先通过"初始标记"的"非垃圾对象"变为"垃圾对象",而此时gc收集
器无法检测到,则该垃圾对象被称为"浮动垃圾".
- 错标:
在"初始标记"的垃圾对象,在"并发标记"阶段因为用户线程的影响变为了非垃圾对象.
此时称为错标!
注意:
(1)浮动垃圾无所谓,在下次垃圾回收的时候会把该垃圾回收.不会有什么特别的影响.
但是"错标"的线程,会导致要使用的对象被回收了.就会造成影响.
所以接下来的"重新标记"阶段就是解决"错标"的问题!!!
(2)"初始标记"和"重新标记"都会产生极其短暂的STW.但可忽略不计!
(3)当CMS并发处理失败的时候,会立马切换Serial Old来清理!(替补!!)
(4) CMS解决错标问题采用了"增量更新"的方式.对于那些黑色增加对白色的引用,会通过写屏障的
技术记录下来,再重新标记阶段进行以黑色为根重新扫描一遍,来解决错标的问题!
CMS缺点,不好的地方:
(1)内存碎片
(2)浮动垃圾
(3)如果CMS运行期间预留的空间不足以让用户线程分配一个对象,则并发失败.
并发失败时会先冻结用户线程,然后启动Serial Old来收集老年代.
总结:
(1)CMS解决了什么样的问题??
- 解决了STW时间过长的问题,使垃圾回收时用户的等待时间变短
(2)jdk 1.8之前都是分代垃圾回收,所以就是这么分代垃圾回收器.
jdk 1.8之后都是分区垃圾回收.所以G1 ZGC等就没有老年代 新生代这种概念了!
三、CMS并发失败的问题?
在第四阶段并发清理阶段CMS不会STW,依旧gc线程和用户线程一起运行.并不能像别的老年代垃圾回收器那样等待垃圾快满了再回收, 而是在并发清理阶段预留一定的空间给程序运行.当无法为程序分配足够的空间时,则并发失败,冻结用户线程,然后换为Serial Old来清理.
别的垃圾回收器: 我gc线程垃圾回收之后你用户线程再使用,
而CMS就是在并发清理(回收阶段)也会让你用户线程继续跑.所以CMS就需要在垃圾回收阶段预留给用户线程足够的空间来使用,一旦用户线程要使用的空间大于预留的空间,则并发失败!
四、CMS最严重的两个问题?以及优化??
- 标记清除造成的内存碎片问题
晋升失败时候,新生代发生Minnor GC,幸存区中放不下要存活的对象,只能触发对象担保机制,
晋升老年代,由于内存碎片过多,导致大对象没法被放下!
解决: 让CMS进行一定次数的Full GC的后进行一次标记整理的算法,控制住内存碎片的数量.
可以通过参数配置...
- 并发失败时的Serial Old
解决: 降低触发CMS GC的阈值...让其预留够足够的空间!