JVM垃圾回收机制

垃圾回收机制

  • 在虚拟机中,我们创建完不用的对象我们称之为垃圾,虚拟机会帮我们回收这些垃圾,那就让我们看看虚拟机是如何实现垃圾回收的

如何判断对象是否为垃圾

  • 首先判断对象为垃圾的方式有两种,分别是引用计数算法和可达性分析算法
引用计数算法
  • 引用计数算法很简单,就是如果有一个引用指向这个对象,那么这个对象的计数就加1,减少一个引用指向就减1,那么这样就会有一个问题,如果两个对象互相引用,那么这两个对象变成垃圾这不会被回收,就会造成内存泄露
  • 我们常用的HotSpot虚拟机并没有使用这种方法,而是使用下边这种方式
可达性分析算法
  • 可达性分析算法是通过GC Roots来实现,通过GC Roots引用的对象,我们就标记这个对象,然后一直往下找,如果扫描结束以后,没有被标记的对象我们就称之为不可达,也就是垃圾,然后就可以进行回收
  • GC Roots就是正在使用的引用,可以成为GC Roots的有栈中局部变量表引用,静态引用,常量引用,分代引用等等。

垃圾回收算法

  • 垃圾回收算法分别有三种:复制,标记-清除,标记-整理
  • 内存分配策略有两种,如果内存是规整的则使用指针碰撞分配,如果内存不规整则使用空闲列表分配,分配策略与垃圾回收算法息息相关
复制
  • 复制算法是将一块区域中数据,经过可达性分析算法以后找出存活的对象,复制到另一块区域,最后清除原区域的所有对象
  • 通过这种算法我们发现,存活的对象越多,我们这个方式也就耗费时间越多,我们知道新生代的对象都是朝生夕死的,所以这种算法只能适用于新生代,而不适用于老年代
  • 在HotSpot虚拟机中,新生代是分为三个区域,Eden区和两个Survivor,Survivor区又分为From区和To区。通过可达性分析算法标记了存活对象以后,将Eden区和From区中被标记的对象,复制到To区,然后清除Eden区和From区,并把From区和To区引用交换,这样就完成了一次新生代的垃圾回收
标记-清除
  • 标记-清除这种算法是通过可达性分析算法找到被标记的存活对象,然后清除所有未被标记的垃圾,这种算法是适用于老年代的
  • 这种算法的缺点是最后整理出来的空间不连续,假如存活的对象分布的非常零散,那么就有可能进来一个对象放不下然后触发GC。不过这种算法优点就是比较快,只需要清除就可以
标记-整理
  • 标记-整理这种算法跟标记-清除类似,都是作用于老年代。不过这种算法在清除完垃圾以后会对还存活的对象进行整理,所以弥补了标记-清除的缺点,不过这种方式没有标记-清除速度快,但内存是比较整齐的

垃圾回收器

在这里插入图片描述

  • Serial GC,Parallel Scavenge GC和ParNew GC是新生代GC
  • Serial Old GC,Parallel Old GC和CMS GC是老年代GC
  • G1包括新生代和老年代
Serial GC和Serial Old GC

在这里插入图片描述

  • Serial GC和Serial Old GC是串行回收的,分别作用在新生代和老年代,是客户端下的默认垃圾回收器,在单核CPU下有不错的表现,在垃圾回收时会进行STW(Stop-The-World)
  • STW就是暂停所有用户线程
  • Serial GC使用复制算法,Serial Old GC使用标记-整理算法
  • Serial Old GC也是作为CMS后备垃圾回收器
  • -XX:+UseSerialGC开启串行垃圾回收器
Parallel Scavenge GC和Parallel Old GC

在这里插入图片描述

  • Parallel Scavenge GC和Parallel Old GC是并行回收,目的是达到可控制的吞吐量,主要是吞吐量优先。高吞吐量可以高效率利用CPU时间,适合在后台运算交互不多的场景下
  • Parallel Scavenge GC使用了复制算法,Parallel Old GC使用了标记-整理算法
  • -XX:UseParallelGC 指定新生代使用Parallel Scavenge GC
  • -XX:UseParallelOldGC 指定老年代使用Parallel Old GC,这两个参数设置一个另一个也会被激活
  • -XX:ParallelGCThreads 设置新生代并发垃圾回收的线程个数,CPU小于8个线程默认为CPU个数,大于8个3+((5+CPU_COUNT) / 8)
  • -XX:MaxGCPauseMillis 设置停顿时间 单位毫秒
  • -XX:GCTimeRatio 垃圾收集时间占总时间的比例
  • -XX:+UseAdaptiveSizePolicy 设置自适应调节策略
ParNew GC

在这里插入图片描述

  • ParNew GC也是并行回收,基于新生代,相当于Serial GC的多线程版。也是使用的复制算法
  • -XX:UseParNewGC 指定使用ParNew GC
    • -XX:ParallelGCThreads 设置新生代并发垃圾回收的线程个数,默认为CPU个数
CMS GC
  • CMS GC是一款真正意义上的并发垃圾回收器,可以与用户线程一起工作,追求低延迟的回收器。使用了标记-清除算法,适用于老年代,CMS GC只能跟Serial GC和ParNew GC一起工作。在JDK1.9中标记为废弃
    在这里插入图片描述
  • 工作流程:
  1. 初始标记:初始标记是标记那些直接被GC Roots关联的对象,所以速度非常快
  2. 并发标记:这个阶段是跟用户线程并行的,标记那些存活的对象
  3. 重新标记:由于并发标记阶段是跟用户线程一起执行的,难免标记的不够准确,在这个阶段修正
  4. 并发清除:跟用户线程并行清理垃圾
  • 由于CMS在清除垃圾时需要跟用户线程一起工作,所以就不能等到完全满了以后再开始清除,1.8默认 开始清除,如果垃圾清理不过来,那么就会触发CMS的后置垃圾回收器Serial Old GC,我们知道这个垃圾回收器是串行回收的所以速度比较慢
  • CMS的弊端
  1. 使用了标记-清除算法,所以容易产生内存碎片,会提前触发FullGC
  2. 由于是跟用户线程一起清除的,所以在清除阶段产生的新垃圾是清除不了的,只能等待下一次GC才能清除
  3. 在垃圾回收时吞吐量会下降,因为CPU需要额外执行垃圾回收机制
  • -XX:+UseConcMarkSweepGC 指定CMS回收器,开启后新生代会默认使用ParNew GC
  • -XX:CMSInitiatingOccupanyFraction 设置开始垃圾回收的阈值,1.5之前默认68,1.6以后默认92,如果内存增长缓慢则设置稍大的值,避免CMS的触发次数,增长快速应该调低,避免FullGC
  • -XX:ParallelCMSThreads 设置CMS的线程数量
G1
  • G1是一个全堆的垃圾回收器,设计的初衷是保证可控的情况下获得尽量高的吞吐量,是JDK1.9的默认垃圾回收器
  • G1是把整个堆分成了一个个的区域(region),可以把这些不同的区域当做Eden区,两个Survivor区和老年代。根据每个region的回收价值然后会在后台维护一个优先列表,回收价值就是回收这块区域需要的时间以及回收所获得的空间,每次回收根据允许的时间,优先收集价值最大的region
  • 这种回收侧重于垃圾,所以我们叫垃圾优先(Garbage First G1)
  • region采用标记-复制算法,整体看起来也能叫标记-整理算法,不会因为大对象放不进去而触发FullGC
  • 因为G1通过回收region,而region是一块块小的内存区域,所以对于时间能有更好的控制
  • G1的弊端:用户程序运行过程中垃圾收集产生的内存占用和额外执行负载要比CMS高,总体来说小内存CMS优于G1,平衡点6-8G之间
  • -XX:+UseG1GC 指定使用G1收集器
  • -XX:G1HeapRegionSize 设置region大小,2的幂次方,1M-32M之间,默认堆内存的2000分之1
  • -XX:MaxGCPauseMIllis 期望的GC停顿时间,默认200ms
  • -XX:ParallelGCThread 设置STW工作线程值 ,最多为8
  • -XX:ConcGCThreads 设置并发线程数
  • -XX:InitiatingHeapOccupancyPercent 设置垃圾回收阈值,默认是45
  • 适用场景:适用于服务端,大内存的情况
  • 回收过程:新生代GC->新生代GC和并发标记过程->混合回收
    • 新生代
    1. 扫描根节点:扫描GC Roots,包括RSet(记忆集,记录某个region是否被别的region指向)
    2. 更新记忆集:根据脏卡表来更新记忆集,更新过后可以准确的反应出老年代对对象的引用。卡表(在内存中放入对象时会更新卡表,卡表被更新就变脏,表示有数据被引用了)
    3. 处理记忆集:指向的对象认为是存活的对象
    4. 复制对象:将存活对象复制到一块新的region中。对象年龄会加1,如果年龄达到会进入老年代,如果Survivor区空间不足也会进入老年代
    5. 处理引用:处理软弱需,JNI等引用
    • 并发标记过程:在内存使用达到45以后,开始老年代的并发标记,标记完成后会开始混合回收,也就是新生代和老年代一起进行回收。G1并发标记老年代时不会扫描所有region,只会扫描一部分然后根据停顿时间进行选择性回收
    1. 初始标记:标记GC Roots直接可达的对象,是STW的,并且会触发新生代GC
    2. 根区域扫描:扫描Survivor区可以直达的老年代对象,并标记被引用的对象
    3. 并发标记:会计算每个区域存活对象的比例。如果发现某一个区域所有对象都是垃圾则直接回收
    4. 再次标记:修正并发标记的标记结果,是STW,采用了初始快照算法
    5. 独占清理:计算并排序回收比例,这个阶段并不会实际去清理
    6. 并发清理:识别并清理完全空闲的区域
    • 混合回收:
    1. 并发标记结束以后,老年代中全是垃圾的会立马被回收,没有被回收的会被默认分为8次来回收
    2. 混合回收会回收新生代和8分之1的老年代
    3. 老年代区域垃圾占比越多的会优先回收,不过必须垃圾占比在65以上,不然不会进行回收
    4. 默认允许整个堆内存浪费百分之10的内存,意思是如果可回收垃圾的比例低于百分之10,则不进行混合回收
  • 也有可能会触发Full GC,
  1. 内存放不下
  2. 在并发执行期间内存已经不足

垃圾回收器总结

在这里插入图片描述

垃圾回收日志命令

  • -XX:+PringGC 输出GC日志
  • -XX:+PringGCDetails 输出GC详细日志
  • -XX:+PrintGCTimeStamps 输出GC时间戳
  • -XX:+PringGCDateStamps 输出GC时间戳 以日期形式
  • -XX+PrintHeadAtGC GC前后输出堆信息
  • -Xloggc:…/logs/gc.log 输出到日志文件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值