JVM垃圾回收

垃圾回收基本原理

GC(Garbage Collection:垃圾回收)基本原理:将内存中不在使用的对象进行回收,GC中用户回收的方法称之为回收器,由于GC需要消耗一定的资源和时间的,GC主要作用于堆空间,根据对象的生命周期的特征进行分析按照新生代,老年代的方式来对对象进行收集,尽可能少的缩短GC操作对应用程序的暂停

四种回收类型

不同的对象引用类型,GC采用的不同的方法进行回收,JVM对象引用分为四种类型:
强引用:默认情况下,对象采用的是强引用,在对象没有其他引用时,GC才会回收,当对象有至少一个引用时,JVM即使抛出异常也都不会回收该对象
软引用:软引用所作用于的对象,在内存不足时才会被回收,一般适用于缓存场景
弱引用:弱引用作用的对象,只要发生GC就一定会被回收
虚引用:虚引用只是用来得知对象是否被GC

对象被标记为垃圾的方法

引用计数法

引用计数法是垃圾回收器早期采用的策略,在这种方法下,堆中的每一个对象都有一个引用计数,当一个对象被创建时,就给对象实例分配一个变量,将引用计数设置为1,地方任何其他变量被赋值为这个对象的引用时,计数加1,当一个对象实例的引用被设置为一个新的值时,对对象计数-1,当计数为0时,表示当前对象没有引用了,就可以将该对象视为垃圾,当垃圾回收器进行回收时,就可以回收该对象。
缺点:无法检测循环引用的问题
如图:
在这里插入图片描述

在这里插入图片描述

可达性分析

可达性分析是将程序中所有的引用关系看成一张图,从一个GC Roots开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点,当所有的引用节点被查找完毕之后,剩余的节点认为是没有用的节点,无用的节点则被发任务是可回收的对象

可作为GC Roots的对象包含以下几种:
• 虚拟机栈中的引用对象(栈帧中的局部变量表)
• 方法区中的类静态属性引用的对象/方法区中常量的引用对象
• 本地方法栈中JNI引用的对象
在这里插入图片描述
如图:对象实例中1、2、4、6都具有对象可达性,也就是存活对象,不能被GC回收的对象,而实例3、5虽然相连,但是没有一个GC Roots与之相连,这些实例是会被GC回收的对象

垃圾回收算法

标记-清除算法

标记-清除算法整个将回收分为两个节点,分为“标记”和“清除”阶段,标记阶段标记处所有的需要被回收的对象,在清除阶段统一回收未被标记的对象
标记阶段:标记的剁成使用可达性分析算法的过程,遍历所有的GC Roots对象,对从GC Roots对象可达的对象都做上一个标记,一般在对象的header中,将其标记为可达对象
清除阶段:对堆内存进行整个遍历,如果发现某个对象没有被标记为可达的对象(通过读取header中的信息),则将其进行回收
在这里插入图片描述

缺点

• 空间问题
标记清除会产生大量的不连续的内存碎片,内存碎片化太多空间可能会导致后续的程序在运行过程中分配大对象施,无法找到足够的连续空间而不得不触发一次垃圾回收过程
• 效率问题
在标记和清除阶段都要遍历整个堆空间,在堆空间中对象数量特别大时,对堆空间的遍历无疑是和消耗时间的,而且GC过程会导致应用程序停顿

复制算法

复制算法将堆空间按照容量大小划分成大小相同的两块,每次只使用其中一块,当一块使用内存完了,就将还存活的对象复制到另一块内存中,然后将这一块内存中所有的对象一次性清除掉
优点:简单高效,并且优化了标记清除算法的效率低,内存碎片化多的问题
缺点:
• 将内存缩小为原来的一般,浪费了一般的内存空间,代价太高
• 如果对象存活率比较高,极端一点假设对象100%存活,就需要将所有的对象复制一遍,消耗的时间代价也比较大

标记-整理算法

标记-整理算法和标记-清除算法很像,标记-整理的算法在标记的过程和标记-清除算法的标记阶段一样,后续步骤整理阶段不是直接对可回收的对象进行回收,而是让所有存活的对象向一端移动,然后直接清理掉另一端的内存
优点:弥补标记-清除阶段的内存碎片化的问题,避免了复制算法内存减半的代价
缺点:效率不高:不仅要标记存活对象,还要管理所有的存活对象的引用地址,在效率上不如复制算法

分代回收算法

分代回收算法思想按照对象存活的生命周期不同将内存划分为不同的几块,一般划分为新生代、老年代(永久代)
特点:

新生代:朝生夕灭,存活的时间很短,采用复制算法来回收
老年代:经过多次Minor GC而存活下的对象,存活周期长

标记清除算法或者标记整理算法采用的回收算法基于回收器来决定的,新生代每次垃圾回收都有大量的对象死去,只有少量的存活,采用复制算法来回收新生代,只需要付出少量的对象复制成本就可以完成收集,而老年代中对象存活率比较高,不适用复制算法,如果采用复制算法它没有额外的空间进行分配担保,因此老年代必须使用标记清除或者标记整理算法进行回收
在这里插入图片描述现在虚拟机都是采用复制算法来回收新生代,由于新生代的对象存活率比较低,所以新生代并不是按照1:1来划分内存空间,而是将内存区域划分为一块较大的Eden区域和两块较小的From Survivor区域和To Survivor区域,三者比例是8:1:1,每次使用Eden和From Survivor区域,To Survivor作为保留区域
GC操作时,将Eden区域的存活对象复制到To Survivor区域,而在From Survivor区域中,仍存活的对象会根据他的年龄决定去向,年龄值达到年龄阈值(默认15,新生代对象每经过一次垃圾回收,年龄值加1)对象会被复制到老年代,没有达到年龄阈值的对象会被复制到To Survivor区域,然后将Eden区域和From Survivor区域清空,新生代对象都存在于To Survivor区域,接着To Survivor和From Survivor交换角色,总之,不管怎样都要保证To Survivor区域时保留区域即经过一次GC之后是空的。

内存的分配和回收

分配策略:

• 对象分配优先分配Eden区域
• 大对象直接进入老年代
• 长期存活的对象进入到老年代
• 动态对象年里判定(年里超过阈值或者Survivor空间相同年龄段的所有对象大小总和大于Survivor区域一半,大于或等于该年龄的对象直接进入到老年代)

内存分配担保机制:

在执行Minor GC前, JVM会首先检查Tenured是否有足够的空间存放新生代尚存活对象,由于新生代使用复制收集算法,为了提升内存利用率,只使用了其中一个Survivor作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况时,就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代,但前提是老年代需要有足够的空间容纳这些存活对象。
但存活对象的大小在实际完成GC前是无法明确知道的,因此Minor GC前, JVM会先首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小,如果条件成立, 则进行Minor GC,否则进行Full GC(让老年代腾出更多空间)。然而取历次晋升的对象的平均大小也是有一定风险的,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败(Handle Promotion Failure,老年代也无法存放这些对象了),此时就只好在失败后重新发起一次Full GC(让老年代腾出更多空间)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值