首页 > 编程知识 正文

jvm有哪些垃圾回收算法,java垃圾回收算法有哪些

时间:2023-05-04 23:28:11 阅读:16430 作者:1879

在上一篇文章中,我们分析了JVM运行时数据区域,大致了解了每个JVM内存区域中存储的数据,以及如何在Java堆中创建、布局和访问对象。 具体请参照《【JVM系列一】深入理解JVM内存模型,看完这篇足以!》。 本文主要分析JVM垃圾回收的相关内容。 那么,在进入正文之前,让我们考虑一下。

我需要回收哪个内存? 什么时候回收? 怎么回收? 一、看看概要吧。JVM体系结构概览图

其中,程序计数器、虚拟机堆栈和本地方法堆栈这三个区域都是线程专用的,因此生命周期与线程相同。 随着堆栈的栈帧方法的开始和结束,正在认真执行堆栈和堆栈操作。 每个堆栈帧分配多少内存,基本上在类结构确定时是已知的。 因此,这些空间的内存分配和回收是确定性的,并且很少考虑垃圾回收。 因为在方法结束或线程结束时,内存会自然回收。

Java堆和方法空间不同,一个接口中多个实现类所需的内存可能不同,一个方法中多个分支所需的内存也可能不同。 只有当程序位于运行期上时,才能看到将创建这些对象。 这部分的内存分配和回收均为3358www.Sina.com/

二、在判断对象在进行垃圾存储器回收前生存之前,必须考虑如何判断哪个对象还活着,哪个对象死了。 如果知道了这个问题,就知道哪个内存可以回收利用。 因此,我将介绍判断对象生存的方法。

2.1参照计数法向对象添加参照计数器,如果禁用了每次某个地方参照时将计数器值加1的参照,则计数器值减1。 计数器值为0表示对象未使用且可以回收。

但是主流的JVM不使用引用计数算法来管理内存。 主要原因是难以解决动态的问题。

如果有a类和b类对象,每个对象都有一个实例字段,值为:

objectA.instance=objectB; objectB.instance=objectA; 即使不使用,由于相互参照对方,参照计数值不为0,参照计数法也不能通知GC收集器将它们回收。

如果将objectA和objectA都直接设置为null,则Java堆中的这两个对象内存仍为交叉引用,无法重用,如下图所示。

2.2可达性分析算法在主流商用程序语言主流实现中,采用Java、C# (均采用可达性分析法判断对象是否生存。

设计思路:一系列“互相循环引用”对象为GC Roots,从这些节点出发的路径称为起始点。 如果对象未通过参照链连接到GC Roots,则该对象不可用。

如下图所示,对象object5、object6、object7相互关联,但无法到达GC Roots,因此被判定为可复用对象。

在Java中,可以用作GC Roots的对象如下:

堆栈(堆栈帧中的本地变量表(基本数据类型、引用类型) )引用的对象的本地方法堆栈中的JNI ) )引用的对象方法区域中的类静态属性

因为这些对象一定不会被回收。 例如,虚拟机堆栈中国正在执行的方法,因此不会回收中引用的对象。

如何避免智能未来世代对象被旧年代对象引用时被gc引用? (联系上一代对象,确保对聪明的未来一代对象的引用不会被错误回收) ) ) ) )。

由于在老一代幸存者较多的情况下,每次minor gc调查老一代的所有对象时都会影响gc效率(因为gc stop-the-world ),所以老一代有写入障碍(write barrier )进行管理的card table (写入障碍) 由于card table的所有前几代目标都是聪明的未来几代目标,因此minor gc每次都会联系card table,以避免查询整个旧年代,从而提高gc的性能。

引用链(Reference Chain)

(引用: https://blog.csdn.net/Wu Zhiwei 549/article/details/80561208 )

即使是用可达性分析算法无法达到的对象,也并不是“必须死”。 此时,他们暂时处于“缓刑”阶段,实际上要宣布对象死亡,至少要经过重新标记的过程。

标记的前提是在对象进行可达性分析后,发现没有与GC Roots连接的参考链。

1 ) .进行第一次标记和筛查。

的条件是是否必须对此对象执行finalize ()方法。

>     当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。

  2).第二次标记
    如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
    Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
流程图如下:

2.3 再谈引用

引用计数法与可达性分析法都与“引用”相关,在JDK1.2之前,Java引用的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义太过狭隘,只存在“被引用”和“没有被引用”两种状态,但是经常会有一些“食之无味,弃之可惜”的对象:当内存空间足够时,可以保留这些对象;当内存空间在进行垃圾回收后仍然不足,则可以抛弃这些对象。

因此,在 JDK 1.2 之后,引用概念进行了扩充,分为:强引用、软引用、弱引用、虚引用,强度依次减弱。

强引用

程序中普遍存在,类似“Object obj = new Object()” 所创建的引用,只要强引用还存在,GC就不会回收被引用的对象。

软引用

软引用用来描述一些还有用但并非必需的对象,在系统进行垃圾回收后,内存仍然不足,将要发生内存溢出异常前,将会把这些对象列进回收范围以便进行第二次回收。

JDK1.2 后,提供了SoftReference 类实现软引用。

弱引用

弱引用也是用来描述非必需对象,但强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾回收前。当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。

JDK1.2 后,提供了WeakReference 类实现软引用。

虚引用

虚引用也称为幽灵引用或幻影引用,最弱。

无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

JDK1.2后,提供了PhantomReference 类实现虚引用。

2.4 生存还是死亡

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时出于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象建立关联即可。

注:任何一个对象的finalize() 方法只会被系统自动调用一次。

2.5 回收方法区

在堆中,尤其是在伶俐的未来代中,一次垃圾回收一般可以回收 70% ~ 95% 的空间,而永久代的垃圾收集效率远低于此。

永久代垃圾回收主要两部分内容:废弃的常量和无用的类(方法区的内存回收主要是针对常量池的回收和对类型的卸载)。

注:永久代并不等价于方法区,只是JVM将GC分代收集扩展至方法区,或者说使用永久代实现方法区而已。

判断废弃常量:回收废弃常量与回收Java堆中的对象类似,一般是判断该常量没有任何地方被引用。

判断无用的类,需要同时满足以下3个条件:

该类所有的实例都已经回收,也就是 Java 堆中不存在该类的任何实例。加载该类的 ClassLoader 已经被回收。该类对应的 java.lang.Class 对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

在大量使用反射、动态代理 、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景,都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

三、垃圾收集算法 3.1 标记-清除算法

主要分为“标记”与“清除”两个阶段,而对象标记的判定在上面已经介绍过(引用计数法、可达性分析法)。

原理:

标记:从GC Roots 根集合开始扫描,对存活的对象进行标记(存活对象存在与GC Roots相连的引用链)。

清除:扫描整个内存空间,回收未被标记的对象,使用free-list(空闲列表-内存交错不规整)记录可用区域。

标记-清除算法示意图,如下:

优点:不需要额外的空间。

缺点:

效率低:标记与清除两个阶段都需要扫描,效率低。内存碎片:清除后会产生大量不连续的内存碎片,这样会导致在后续分配大对象时,无法找到足够的连续内存而不得不提前触发GC。3.2 复制算法

为了解决效率问题,出现“复制”算法。

它可将可用内存空间分成大小相等的两块,每次只使用其中一块。当这块内存使用完时,在触发GC时,就会将还存活的对象复制到另一块上面,并将已使用过的内存清除掉。

如下图,采用复制算法GC,内存空间被划分为大小相等的两块区域,回收前,左半部分有5个对象仍然存活,回收后被复制到右半部分,并清理掉左半部分:

但是,因为大多数伶俐的未来代对象都是“朝生夕死”,熬不过第一次 GC。所以没必要 1 : 1 划分空间。可以分一块较大的 wrdgz 空间和两块较小的 Survivor 空间(Survivor From、Survivor To),每次使用 wrdgz 空间和其中一块 Survivor。当回收时,将 wrdgz 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上,最后清理 wrdgz 和 刚才用过的Survivor 空间。它们在HotSpot VM 中默认的大小比例是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。

原理:

从根集合(GC Roots)开始,通过Tracing从wrdgz、Survivor From区中找到存活对象,拷贝到Survivor To中;From、To交换身份,下次内存分配从To开始。

优缺点:解决了标记-清除算法的效率低问题,但是会造成空间利用率低;如果不想浪费50%的内存空间,则还需要额外的内存空间作为分配担保。

3.3 标记-整理算法

复制算法针对对象存活率高的情况就需要较多的复制操作,反而效率变低。更关键的是,如果不想浪费50%的空间,则需要额外的空间进行分配担保,以应对被使用的内存中所有对象100%存活的极端情况,所以老年代一般不采用该算法。

不同于针对伶俐的未来代的复制算法,针对老年代的特点,创建该算法。主要是把所有存活的对象都移到内存的同一端,并清理掉边界以外的内存。

原理:

标记:与标记-清除算法一样,从GC Roots 根集合开始扫描,对存活的对象进行标记。

整理:再次扫描,并把存活对象统一往一端移动,然后清理掉边界以外的内存。

优缺点:没有内存碎片产生,无需分配担保的额外空间,节省内存空间;但需要移动对象的成本,效率低。

如下,是采用标记-整理算法GC的示意图:

3.4 分代收集算法

目前主流的商业虚拟机都是采用“分代收集”的垃圾算法,它主要是根据对象存活周期的不同将堆内存划分为几块。一般是将Java堆分为伶俐的未来代与老年代,根据不同那个年龄代对象的特点,采用不同的回收算法。在伶俐的未来代中,每次GC时都会有大部分对象死去,只有少量存活,因此选用复制算法,只需要付出少量存活对象的复制成本就可以完成回收。而老年代中,因为对象存活率高、也没有额外的空间对它进行分配担保,所以选用“标记-清除”或“标记-整理”算法进行GC。

什么是分配担保机制?

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于伶俐的未来代所有对象的总空间(分配担保是老年代为伶俐的未来代作担保),

如果大于,则此次Minor GC是安全的。

如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

为什么要进行空间担保?

是因为伶俐的未来代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后伶俐的未来代中所有对象均存活),而Survivor空间是比较小的(8:1:1),这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。

由于篇幅原因,本文先介绍JVM常用的垃圾回收算法,下一篇再详细介绍HotSpot VM中分代收集的算法实现。

《【JVM系列三】HotSpot JVM的垃圾回收算法实现-JVM垃圾回收器》

参考:《深入理解Java虚拟机》

●史上最强Tomcat8性能优化

●阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路

●B2B电商平台--ChinaPay银联电子支付功能

●学会Zookeeper分布式锁,让面试官对你刮目相看

●SpringCloud电商秒杀微服务-Redisson分布式锁方案

查看更多好文,进入公众号--撩我--往期精彩

一只 有深度 有灵魂 的公众号0.0

 

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。