前言
在看狂神频道的时候偶然发现下图,感触颇深。特别在当今【程序 = 业务 + 框架】思想盛行的开发者中,夯实基础基础显得格外重要,因此开此专栏总结记录。
对于对于JVM的学习,由于在工作中的业务场景几乎使用不到,所以总是学了忘忘了学,争取早日理解哈哈,学习博客:
- 厚积薄发打卡Day62 :【狂神】狂神JVM快速入门(上)<从JVM体系到native方法>
- 厚积薄发打卡Day63 :【bugstack & 狂神】狂神JVM快速入门(中)<从‘程序计数器’到‘Dump内存快照’>
- 厚积薄发打卡Day64 :【狂神 & bugstack】狂神JVM快速入门(下)<GC机制与算法>
常用GC算法介绍
分代回收:
前面提到过Java的堆内存被分代管理,那为什么要分代管理呢?分代管理主要是为了方便垃圾回收。
这样做是基于两个事实:
- 第一是大部分对象很快就不再使用了
- 第二是还有一部分不会立即无用,但也不会持续很长时间
因此,虚拟机中划分为年轻代、老年代和永久代,我们来看图:
-
年轻代:
- 主要用来存放新创建的对象
- 年轻代分为eden 区和两个survival 区,大部分对象在Eden 中生成
- 当Eden区满时,对象会在两个survival 区交替保存,达到一定次数后,对象会晋升到老年代
-
老年代:
- 用来存放从年轻代晋升而来的存活时间较长的对象
-
永久代前面也介绍过主要保存类信息等内容
- 这里的永久代是指对象划分方式,不是专指1.7的Permanent Generation 或者1.8 之后的meta space
-
根据年轻代与老年代的特点:
- JVM提供了不同的垃圾回收算法:
- 垃圾回收算法按类型可以分为:
- 引用计数法
- 引用计数法是通过对象被引用的次数来确定对象是否还在被使用
- 缺点是无法解决循环引用的问题
- 复制法标记
- 复制算法需要from 和to 两块儿大小相同的内存空间,对象分配时只在form 块中进行
回收时把存活对象复制到 to 块中,并清空from 块,然后交换两块的分工 - 把from 块儿作为to块,把to 块儿作为from 块
- 缺点是内存使用率较低
- 复制算法需要from 和to 两块儿大小相同的内存空间,对象分配时只在form 块中进行
- 标记清楚算法:
- 标记清除算法分为标记对象和清除不再使用的对象两个阶段
- 标记清除算法的缺点,是会产生内存碎片
- 引用计数法
-
JVM中垃圾回收器
- 新生代:
- Serial
- 算法:标记-复制算法
- 说明:简单高效的单核机器,Client 模式下默认新生代收集器;
- Parallel ParNew
- 算法: 标记-复制算法
- 说明:GC 线程并行版本,在单 CPU 场景效果不突出。常用于 Client 模式 下的 JVM
- Parallel Scavenge
- 算法:标记-复制算法
- 说明:目标在于达到可控吞吐量(吞吐量=用户代码运行时间/(用户代码运 行时间+垃圾回收时间))
- Serial
- 老年代
- Serial Old
- 算法:标记-压缩算法
- 说明:性能一般,单线程版本。1.5 之前与 Parallel Scavenge 配合使用;作 为 CMS 的后备预案。
- Parallel Old
- 算法:标记-压缩算法
- 说明:GC 多线程并行,为了替代 Serial Old 与 Parallel Scavenge 配合使 用。
- CMS
- 算法:标记-清除算法
- 说明:对 CPU 资源敏感、停顿时间长。标记-清除算法,会产生内存碎 片,可以通过参数开启碎片的合并整理。基本已被 G1 取代
- Serial Old
- G1
- 算法:标记-压缩算法
- 说明:适用于多核大内存机器、GC 多线程并行执行,低停顿、高回收效率。
- 新生代:
垃圾回收算法:
下面我们详细介绍几个典型的垃圾回收算法
cms 回收算法
- CMS 是JDK1.7以前可以说最主流的垃圾回收算法
- CMS 使用标记清除算法你,优点是并发收集停顿,我们看图中CMS 的处理过程:
- CMS 的第一个阶段是初始标记,这个阶段会stop the word 标记的对象只是从root,最直接可达的对象
- 第二个阶段是并发标记,这时GC 线程和应用线程并发执行主要是标记可达的对象
- 第三个阶段是重新标记阶段,这个阶段是第二个stop the world 阶段,停顿时间比并发标记要小很多,但比初始标记稍长,主要对对象进行重新扫描并标记
- 第四个阶段是并发清理阶段进行并发的垃圾清理,最后一个阶段是并发重置阶段,为下一次GC重置相关数据结构
- CMS 使用标记清除算法你,优点是并发收集停顿,我们看图中CMS 的处理过程:
G1算法:
- 这一算法在jdk1.9 后成为了JVM的默认垃圾回收算法
- G1的特点是保持高回收率的同时将,减少停顿记忆算法,取消了追踪年轻代与老年代的物理划分,但它仍然属于分代收集器,这一算法将对于分为若干个区域,称为region
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MOTEJ5Mb-1627385873502)(厚积薄发打卡Day76 :【MSUP】深入浅出JVM(下)常用GC算法及考察点/
)] - 如图中的小方格所示:
- 一部分区域用作年轻代
- 一部分用作老年代
- 还有另外一种专门用来存储巨型对象的分区
- G1和CMS 一样会遍历全部对象,然后标记对象一种情况,在清除对象后会对区域进行复制,移动整合碎片空间
- 图的右边是这一年轻代与老年代的回收过程:
- G1的年轻代回收采用复制算法并行进行收集,收集过程会stop the world,这一个老年代回收
- 同时也会对年轻代进行回收,主要分为四个阶段:
- 第一个阶段依然是初始标记阶段,完成对根对象的标记这个过程,这时会 stop the word
- 第二个阶段并发标记阶段,这个阶段是和用户线程并行执行的
- 第三个阶段最终标记阶段,完成三色标记的标记周期
- 第四阶段复制/清除阶段,这个阶段会优先对可回收空间较大的region进行回收,即garbage first(G1),这也是记忆名称的由来
- 这一采用每次只清理一部分,而不是全部religion 的增量式清理,由此来保证每次gc停顿时间不会过长
- 图的右边是这一年轻代与老年代的回收过程:
- 总结一下这一算法,这部分需要掌握
- 这一是逻辑分代,不是物理分代
- 需要知道回收的过程和停顿的阶段
- 此外还需要知道G1算法,允许通过JVM参数设置region 的大小范围是1~32M(兆)
- 还可以设置期望的最大这些停顿时间等等
- 如果你有兴趣:也可以对gms 和g1使用的三色标记算法,进行简单的了解
ZGC算法:
- ZGC是最新的JDK1.11中提供的高效垃圾回收算法,ZGC针对大堆内存设计,可以支持tb级别的堆:
- 它非常高效,能够做到十毫秒以下的回收停顿时间
- 这么短的停顿时间,ZGC是如何做到的呢?
-
我们来了解一下ZGC的黑科技:
- ZGC使用了着色指针技术,
- 我们知道,64位平台上一个指针可用位是64位,ZGC限制最大支持4tb的堆,这样寻址只需要使用42位,那么会剩下22位就可以用来保证额外的信息
- 着色指针技术:就是利用指针的额外信息位在指针上对对象进行着色标记
- 第二个特点是使用读屏障:
- ZGC使用读屏障来解决GC线程和应用线程可能并发修改对象状态的问题,而不是简单粗暴的通过stop the world 来做全局的锁定,使用读屏障只会在单个对象的处理上有概率被减速
- 第三个特点是ZGC的并发处理:
- 由于读屏障的使用,进行垃圾回收的大部分时候都是不需要stop the world,因此ZGC的大部分时间都是并发处理
- 第四个特点是基于region
- 这与G1算法一样,不过虽然也分了region,但是并没有进行分代
- ZGC的region不像G1那样是固定大小,而是动态决定region 的大小,region 可以动态创建和销毁,这样可以更好地对大对象进行分配管理
- 第五个特点是压缩整理CMS 算法
- 清理对象时原地回收,会存在内存碎片问题
- ZGC 和G1一样,也会在回收后对region 中的对象进行移动合并,解决了碎片问题
- ZGC使用了着色指针技术,
-
虽然ZGC的大部分时间是并发进行,但还是会有短暂的停顿,来看一下ZGC的回收过程,这张图是按ZGC的回收时序绘制的:
- 我们从上往下看,初始状态是整个堆空间被划分为大小不等的许多region,即图中绿色的方块
- 开始进行回收时,ZGC首先会进行一个短暂的stop the world,来进行如此根对象的标记
- 这个步骤非常短,因为如此的总数量通常比较小,然后就开始进行并发标记
- 如图,通过对对象指针进行着色来进行标记,结合读屏障解决单个对象的并发问题,其实这个阶段在最后的时候还会有一个非常短的stop the world
停顿用来处理一些边缘情况- 这个阶段绝大部分时间都是并发进行的,所以没有明显标示出这个停顿
- 下一个阶段是清理阶段
- 这个阶段会把标记为不可用的对象进行回收,如图,把橘色的不再使用的对象进行了回收
- 最后一个阶段是重定位,
- 重定位就是对这些后存活的对象进行移动,来腾出大块的内存空间解决碎片问题,在重定位最开始会有一个短暂的stop the world,用来重定义集合中的root 对象,暂停时间取决于入头的数量和重定位及与对象的总活动级的比率
- 最后是并发重定位,这个过程也是通过读屏障与应用线程并发进行的
考察点和加分项:
总结一下JVM 相关的面试考察点:
- 需要理解JVM 的内存模型和java 的内存模型
- 要了解类的加载过程,了解双亲委派机制
- 要理解内存的可见性与java 内存模型对原子性可见性,有序性的保证机制
- 要了解常用的这些算法的特点,执行过程和适用场景
- 例如G1适合对最大延迟有要求的场合,ZGC适用于六十四位系统的大内存服务中
- 要了解常用的JVM参数,明白对不同参数的调整,会有怎样的影响,适用于什么样的场景:
- 比如垃圾回收的并发数
- 偏向锁的设置等