一文掌握JVM常见垃圾回收算法与垃圾收集器

从垃圾回收算法、垃圾收集器,这字面意思上来看,都是是围绕垃圾这个名词展示的,那么啥是垃圾呢?

垃圾定义

我们日常生活中是怎么定义垃圾的呢?对于那些不需要用的东西称为垃圾,不然都放在屋里那怎么受的了。对于JVM也是这样的,垃圾就是在程序运行时没有任何指针指向的对象就是垃圾。因为不需要使用它了,留着它也是占内存,赶紧把它清除掉为别的对象腾放空间。

那我怎么知道一个对象是不是垃圾呢?万一把还需要用的对象给清除掉了,那也不是太好啊。所以我们需要一定的方法来确认这个对象是否被引用,进而来确认是否要删除。

两种标识算法

1、引用计数法
这种方法对每个引用的对象维护一个引用计数器,每当引用一下这个对象的时候,就将计数器+1,然后引用失效的时候就减1。
在这里插入图片描述
但是JVM却没有使用引用计数法,这是为啥勒?光看上面的图不太容易看出来,我们看下下面这张图:

在这里插入图片描述
objectA被A与objectC都引用了,所以它的引用次数为2,objectB与objectC是都被引用了一次,所以它的引用次数为1。当A断开连接之后,因为objectA还被objectC引用,就会造成objectA、objectB、objectC之间的循环引用,他们的引用次数都是1,但是按道理来说,A不再引用他们了,他们都该成为了垃圾,然而现在还有引用次数,所以无法当作垃圾清除。正是因为有这个原因,JVM也才没有使用这种方法。

除此之外,这种方法需要单独的字段存储计数器,增加内存空间的开销(影响很小,就是一个变量)。需要更新计数器的值,增加时间开销。当然了,这种方法也是有优点滴: 他实现简单,垃圾对象便于标识。判定效率高,回收没有延迟性。

虽然Java没有使用这种方法,但是Python采用了引用计数算法,python可以手动解除引用关系,这个我们再次就不多讨论了。

2、可达性算法

可达性分析算法(根搜索算法、追踪性垃圾收集),可以有效的解决引用计数算法中循环引用的问题,防止内存泄漏的发生。我们的JVM就是采用的可达性算法。

可达性算法有GC root根节点,根节点可以通过连线直接与对象连接或者间接连接。比如下图中A是直接与GC Root连接,B是通过A与根节点间接连接,无论是直接还是间接,都可以称为可达,也就是一根绳上的蚂蚱。对于那些没有与根节点相连的对象,也就是不可达,也就意味着该对象已经死亡,则会被回收。

在这里插入图片描述

就像图中样子,这样对于那些循环引用的垃圾也会回收。

那么啥是根节点呢?总体来说,根节点可以分为以下几类:

  1. 虚拟机栈中引用的对象:局部变量表中的内容,包括方法参数、局部变量。

  2. 类的静态属性:引用类型的静态变量,JDK8及之后存储在堆上。

  3. 方法区中的字符串常量池:String Table中的引用。

  4. 被synchronized所持有的对象

  5. 常驻的异常类对象(空指针、OOM)、系统类加载器。

说了这么多,其实也是对于JVM的垃圾定义有个简单的认识,接下来就是JVM的垃圾回收算法。

标记清除算法

标记清除算法是垃圾回收算法的基础,别的算法都是在这个基础上进行一些改进。这个算法就如同它的名字一样,分为标记与清除两个阶段。

标记阶段:收集器从引用GC Roots开始递归遍历,标记所有被引用的可达对象,也就是上面我们所说的可达性算法。

清除阶段: 收集器对堆内存从头到尾进行线性遍历,如果发现某个对象在Header中没有标记为可达,则将其回收。

在这里插入图片描述
从图上我们就可以看到,这种方法虽然可以将垃圾对象给清除了,但是就会造成可用内存不连续。这样的话,在后续内存使用中,效率也不是太高。

复制算法

复制算法将内存空间划分为两大块,每次只使用其中一块。将存活的可达对象复制(深拷贝)到未被使用的内存块,并且清理正在使用的内存块中的所有对象交换两个内存块的角色,完成垃圾回收。

复制到另一块内存时,可达对象是紧密排列的。连续的内存空间,在对象创建分配内存时,是通过指针碰撞的方式进行的。

在这里插入图片描述
这就很明显看出来,使用完复制算法后,可用内存都是连续的,不会出现“碎片问题”。

但是也存在缺点,因为是将内存划分为两份,所以也会造成可用内存减半。而且数据的复制也需要额外的时间开销。

标记整理算法

复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生,但是在老年代,更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活对象较多,复制的成本也将很高。

标记清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下而且在执行完内存回收后还会产生内存碎片,所以JVM的设计者需要在此基础之上进行改进因此,基于老年代垃圾回收的特性,标记—整理(压缩)算法(Mark- Compact)算法由此诞生。

标记阶段:和标记清除算法一样,从根节点递归标记所有可达的对象。

整理阶段:将所有的存活对象压缩到内存的一端,按序排放。之后清除边界外的垃圾对象。

标记整理算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark- Sweep Compact)算法

二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策

可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时, JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。

在这里插入图片描述

分代收集算法

当前虚拟机的垃圾收集都采⽤分代收集算法,这种算法没有什么新的思想,只是将之前的几种垃圾收集算法根据实际情况来使用。而这就与JVM中的堆有关。

JVM中的堆分为新生代、from区、to区、老年代。它们的具体情况在此就不多做介绍了。因为新生代每次收集都会有⼤量对象死去,深入理解JVM虚拟机上说98%的对象,不知道是不是这么多,总之就是存活率很低。所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。⽽⽼年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分配担保,所以我们可以选择“标记清除”或“标记整理”算法进⾏垃圾收集。

CMS收集器

如果说收集算法是内存回收的⽅法论,那么垃圾收集器就是内存回收的具体实现。虽然现在垃圾收集器种类有很多,像什么Serial收集器、ParNew收集器、CMS收集器等等。但是最常见的还是CMS收集器、G1收集器。所以本文我们就来看下CMS收集器与G1收集器。

CMS(Concurrent Mark Sweep)收集器是⼀种以获取最短回收停顿时间为⽬标的收集器,它实现了让垃圾收集线程与⽤户线程(基本上)同时⼯作。

CMS收集器是从字面理解就是并发标记清除收集器,实际上它就是采用的 “标记清除”算法实现的。CMS收集器的垃圾收集过程可以分为四个阶段:

初始标记: 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ;

并发标记: 同时开启GC和⽤户线程,⽤⼀个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为⽤户线程可能会不断的更新引⽤域,所以GC线程⽆法保证可达性分析的实时性。所以这个算法⾥会跟踪记录这些发⽣引⽤更新的地⽅。

重新标记: 重新标记阶段就是为了修正并发标记期间因为⽤户程序继续运⾏⽽导致标记产⽣变动的那⼀部分对象的标记记录,这个阶段的停顿时间⼀般会⽐初始标记阶段的时间稍⻓,远远⽐并发标记阶段时间短。

并发清除: 开启⽤户线程,同时GC线程开始对为标记的区域做清扫。

CMS收集器有并发收集、降低STW(Stop-The-World)。但是它有下⾯三个明显的缺点:对CPU资源敏感;⽆法处理浮动垃圾;它使⽤的回收算法-“标记-清除”算法会导致收集结束时会有⼤量空间碎⽚产⽣。

PS:STW也就是说在GC线程进⾏垃圾收集⼯作的时候必须暂停其他所有的⼯作线程,当GC线程将垃圾清除完之后,再运行其它工作线程。因为这个期间其它线程无法工作,也就造成了我们的程序实际上是没有执行我们的业务代码的,所以降低STW的时间越短,对于我们也就越有益。

G1收集器

G1 (Garbage-First)是⼀款⾯向服务器的垃圾收集器,主要针对配备多个处理器及⼤容量内存的机器.以极⾼概率满⾜GC停顿时间要求的同时,还具备⾼吞吐量性能特征。

G1收集器具备以下特点:

并⾏与并发:G1能充分利⽤CPU、多核环境下的硬件优势,使⽤多个CPU(CPU或者CPU核⼼)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执⾏的GC动作,G1收集器仍然可以通过并发的⽅式让Java程序继续执⾏。

分代收集:虽然G1可以不需要其他收集器配合就能独⽴管理整个GC堆,但是还是保留了分代的概念。

空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。

可预测的停顿:这是G1相对于CMS的另⼀个⼤优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建⽴可预测的停顿时间模型,能让使⽤者明确指定在⼀个⻓度为毫秒的时间⽚段内。

G1收集器的运作可以分为:初始标记、并发标记、最终标记、筛选回收。具体的内容与CMS收集器是差不多的。

此外G1收集器在后台维护了⼀个优先列表,每次根据允许的收集时间,优先选择回收价值最⼤的Region(这也就是它的名字Garbage-First的由来)。这种使⽤Region划分内存空间以及有优先级的区域回收⽅式,保证了GF收集器在有限时间内可以尽可能⾼的收集效率(把内存化整为零)。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值