jvm学习的核心(五)---垃圾回收算法和常见垃圾回收器

1.垃圾回收算法

首先,我们需要明确的是,垃圾回收主要包括以下几个阶段

1.1. 标记阶段

在标记阶段,我们举例两个算法
1.引用计数算法
这个算法是python使用的标记算法。

什么是引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1,引用数量为0的时候,则说明对象没有被任何引用指向,可以认定是”垃圾”对象

这种方法实现比较简单,且效率很高,但是无法解决循环引用的问题,因此在java中没有采用此算法(但是在Python中采用的是此算法)

2.可达性分析算法
这个算法是java使用的标记算法。

可达性分析算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
在这里插入图片描述

2.1.对象的finalization机制
在这里插入图片描述

这个机制我称之为救赎机制,当对象不可达的时候是且仅有一次救赎的机会的,如果这个方法重写过且没执行过,就会执行该方法。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queuc的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。

1.2. 清除阶段

1.2.1.标记清除算法

当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

  • 标记:collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
  • 清除:collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。

1.2.2.标记复制算法

  • 将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制另一块去,然后再把使用的空间一次清理掉。
    优点:
    1.不会出现内存碎片问题
    2.对于存活对象少的区域(新生代),简单高效

    缺点:
    1.浪费空间并且移动对象开销大

1.2.3.标记整理算法

根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
优点:
1.消除了标记-清除算法中,内存区域分散不连续的缺点,
2.消除了复制算法中内存减半的高额代价

缺点:
1.移动对象开销较大

1.3.引用

内存溢出的原因:存在大量无用的但又有强引用的对象无法回收

下面介绍java存在的4种引用:

1.强引用
我们经常使用的引用都是强引用,强引用触及的对象,即便程序报错也不可能回收 。

2.软引用
在程序要内存溢出之前,会把软引用对象列入二次回收范围,如果本次回收内存仍然不足以运行程序,则会把软引用对象回收,一般用于高速缓存。

3.弱引用
发现即回收,生存到下次内存回收之前,一般用于可有可无的缓存。

4.虚引用
对程序完全无影响,但是在回收之后可以收到通知

2.常见的垃圾回收器

垃圾回收器的性能指标:
在这里插入图片描述

1:吞吐量:即程序运行时间占总时间的百分比。
2:低延时:单次暂停的时间。

二者属于互斥状态,二者不可得兼
低延迟会缩短回收时间,因而会进行更多回收准备的无用功,因此会对吞吐量产生负面影响。
在这里插入图片描述
注意:CMS在JDK9已经废弃,画虚线的表示在后来发行版本组个方式已经废弃

2.1.Serial回收器

在这里插入图片描述
这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(stop The world)。

  • serial收集器是最基本、历史最悠久的垃圾收集器了。JDK1.3之前回收新生代唯一的选择。
  • serial收集器作为HotSpot中client模式下的默认新生代垃圾收集器。serial收集器采用复制算法、事行回收和"stop-the-world”机制的方式执行内存回收。
  • 除了年轻代之外,serial收集器还提供用于执行老年代垃圾收集的serial old收集器。Serial old收集器同样也采用了串行回收
    和"stop the World"机制,只不过内存回收算法使用的是标记-压缩算法。
  • serial old是运行在client模式下默认的老年代的垃圾回收器
  • Serial old在server模式下主要有两个用途
    • ①与新生代的Parallelscavenge配合使用
    • ②作为老年代CMs收集器的后备垃圾收集方案

2.2.ParNew回收器

在这里插入图片描述

如果说serial cc是年轻代中的单线程垃圾收集器,那么ParNew收集器则是serial收集器的多线程版本。

  • ParNew是Parallel的缩写,New:只能处理的是新生代
  • ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。
  • ParNew收集器在年轻代中同样也是采用复制算法、"stop-the-world"机制。
  • ParNew是很多JVM运行在server模式下新生代的默认垃圾收集器。

2.3.Parallel回收器

Hotspot的年轻代中除了拥有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同样也采用了复制算法、并行回收和"stopthe world”机制。

和ParNew收集器不同,Parallel scavenge收集器的目标则是达
一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
自适应调节策略也是Parallel scavenge与ParNew一个重要区别。

2.4.CMS回收器

在这里插入图片描述

  • 垃圾收集共分为五个阶段:上图所示初始标记和重新标记会有STW。
  • 重新标记,会用到三色标记里的增量更新算法。
  • 并发清理:这个阶段如果有新增对象会被标记为黑色不做任何处理主要优点:并发收集、低停顿。
  • 一XX:+UseConcMarkSweepGC:启用cms

CMS的弊端:
1.会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
2 .CMS收集器对cPu资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
3.CMS收集器无法处理浮动垃圾。可能出现“Concurrent Mode railure"失败而导致另一次 Full GC 的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMs将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行Gc时释放这些之前未被回收的内存空间。

2.5.G1垃圾回收器

G1垃圾回收器,是前沿成果

原因就在于应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有cc就不能保证应用程序正常进行,而经常造成STw的Gc又跟不上实际的需求,所以才会不断地尝试对cc进行优化。G1 (Garbage-First)垃圾回收器是在Java7 update 4之后引入的一个新的垃圾回收器,是当今收集器技术发展的最前沿成果之一。

官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与期望。

  • 因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的Region来表示Eden、幸存者o区,幸存者1区,老年代等。

  • G1 Gc有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个 Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage First) 。

ZGC回收器(实验)

JVM的自动垃圾收集虽然减少了开发人员的工作,在一定程度上减少了内存泄漏的风险,但是由于GC是自动进行的,一些无法预知的事情有时候可能产生对应用有害的影响。

延迟增加导致应用的吞吐量和性能

随着时代发展,硬件会逐渐便宜,应用使用的内存将会越来越大,但是又不能增加延迟,降低吞吐量
ZGC保证,不管在什么情况下,延迟不会超过10毫秒。

ZGC最典型的特性是它是一款并发(concurrent)的GC,其它的特性如下:

它可以标记内存,复制和迁移(relocate)内存,所有的操作都是并发的,同时它有一个并发的引用处理器
其它的垃圾收集器都是使用store barriers,ZGC使用load barriers,用于跟踪内存

lock->unlock->read->load 读内存
use->assign->store->write 写内存

  • ZGC可以更加灵活的配置大小和策略,相比于G1,它可以更好的处理非常大(very large)对象的释放
  • ZGC只有一代,没有新生代,老年代什么的,但是ZGC可以支持局部压缩,在内存恢复和迁移(reclaim and relocate)时,ZGC仍然有很高的性能
  • ZGC依赖NUMA-aware(非均衡存储器访问),需要我们的内存支持这种特点

大部分图片以及内容总结自尚硅谷杜红康老师的尚硅谷宋红康JVM全套教程(详解java虚拟机)

猜你喜欢

转载自blog.csdn.net/faker1234546/article/details/128985770