JVM面试问题系列:判断对象是否已死和四种垃圾回收算法总结

判断对象是否已死

判断对象是否已死就是找出哪些对象是已经死掉的,之后不会再用到的,就像地上有废纸、饮料瓶和百元大钞,扫地前要先判断出地上废纸和饮料瓶是垃圾,百元大钞不是垃圾。判断对象是否已死有引用计数算法和可达性分析算法。java

1.引用计数算法算法

给每个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加 1;每当有一个地方再也不引用它时,计数器值减 1,这样只要计数器的值不为 0,就说明还有地方引用它,它就不是无用的对象。以下图,对象 2 有 1 个引用,它的引用计数器值为 1,对象 1有两个地方引用,它的引用计数器值为 2 。sql

这种方法看起来很是简单,但目前许多主流的虚拟机都没有选用这种算法来管理内存,缘由就是当某些对象之间互相引用时,没法判断出这些对象是否已死,以下图,对象 1 和对象 2 都没有被堆外的变量引用,而是被对方互相引用,这时他们虽然没有用处了,可是引用计数器的值仍然是 1,没法判断他们是死对象,垃圾回收器也就没法回收。数组

2.可达性分析算法安全

了解可达性分析算法以前先了解一个概念——GC Roots,垃圾收集的起点,能够做为 GC Roots 的有虚拟机栈中本地变量表中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中 JNI(Native 方法)引用的对象。性能优化

当一个对象到 GC Roots 没有任何引用链相连(GC Roots 到这个对象不可达)时,就说明此对象是不可用的,是死对象。架构

以下图:object一、object二、object三、object4 和 GC Roots 之间有可达路径,这些对象不会被回收,但 object五、object六、object7 到 GC Roots 之间没有可达路径,这些对象就被判了死刑。并发

上面被判了死刑的对象(object五、object六、object7)并非必死无疑,还有挽救的余地。进行可达性分析后对象和 GC Roots 之间没有引用链相连时,对象将会被进行一次标记,接着会判断若是对象没有覆盖 Object的finalize() 方法或者 finalize() 方法已经被虚拟机调用过,那么它们就会被行刑(清除);若是对象覆盖了 finalize() 方法且尚未被调用,则会执行 finalize() 方法中的内容,因此在 finalize() 方法中若是从新与 GC Roots 引用链上的对象关联就能够拯救本身,可是通常不建议这么作,周志明老师也建议你们彻底能够忘掉这个方法~分布式

3.方法区回收高并发

上面说的都是对堆内存中对象的判断,方法区中主要回收的是废弃的常量和无用的类。

判断常量是否废弃能够判断是否有地方引用这个常量,若是没有引用则为废弃的常量。

判断类是否废弃须要同时知足以下条件:

该类全部的实例已经被回收(堆中不存在任何该类的实例)。

加载该类的 ClassLoader 已经被回收。

该类对应的 java.lang.Class 对象在任何地方没有被引用(没法经过反射访问该类的方法)。

经常使用四种垃圾回收算法

经常使用的垃圾回收算法有四种:标记-清除算法、复制算法、标记-整理算法、分代收集算法。

1.标记-清除算法

分为标记和清除两个阶段,首先标记出全部须要回收的对象,标记完成后统一回收全部被标记的对象,以下图。

缺点:标记和清除两个过程效率都不高;标记清除以后会产生大量不连续的内存碎片。

2.复制算法

把内存分为大小相等的两块,每次存储只用其中一块,当这一块用完了,就把存活的对象所有复制到另外一块上,同时把使用过的这块内存空间所有清理掉,往复循环,以下图。

缺点:实际可以使用的内存空间缩小为原来的一半,比较适合。

3.标记-整理算法

先对可用的对象进行标记,而后全部被标记的对象向一段移动,最后清除可用对象边界之外的内存,以下图。

4.分代收集算法

把堆内存分为新生代和老年代,新生代又分为 Eden 区、From Survivor 和 To Survivor。通常新生代中的对象基本上都是朝生夕灭的,每次只有少许对象存活,所以采用复制算法,只须要复制那些少许存活的对象就能够完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法来进行回收。

在这些区域的垃圾回收大概有以下几种状况:

大多数状况下,新的对象都分配在Eden区,当 Eden 区没有空间进行分配时,将进行一次 Minor GC,清理 Eden 区中的无用对象。清理后,Eden 和 From Survivor 中的存活对象若是小于To Survivor 的可用空间则进入To Survivor,不然直接进入老年代);Eden 和 From Survivor 中还存活且可以进入 To Survivor 的对象年龄增长 1 岁(虚拟机为每一个对象定义了一个年龄计数器,每执行一次 Minor GC 年龄加 1),当存活对象的年龄到达必定程度(默认 15 岁)后进入老年代,能够经过 -XX:MaxTenuringThreshold 来设置年龄的值。

当进行了 Minor GC 后,Eden 还不足觉得新对象分配空间(那这个新对象确定很大),新对象直接进入老年代。

占 To Survivor 空间一半以上且年龄相等的对象,大于等于该年龄的对象直接进入老年代,好比 Survivor 空间是 10M,有几个年龄为 4 的对象占用总空间已经超过 5M,则年龄大于等于 4 的对象都直接进入老年代,不须要等到 MaxTenuringThreshold 指定的岁数。

在进行 Minor GC 以前,会判断老年代最大连续可用空间是否大于新生代全部对象总空间,若是大于,说明 Minor GC 是安全的,不然会判断是否容许担保失败,若是容许,判断老年代最大连续可用空间是否大于历次晋升到老年代的对象的平均大小,若是大于,则执行 Minor GC,不然执行 Full GC。

当在 java 代码里直接调用 System.gc() 时,会建议 JVM 进行 Full GC,但通常状况下都会触发 Full GC,通常不建议使用,尽可能让虚拟机本身管理 GC 的策略。

永久代(方法区)中用于存放类信息,jdk1.6 及以前的版本永久代中还存储常量、静态变量等,当永久代的空间不足时,也会触发 Full GC,若是通过 Full GC 还没法知足永久代存放新数据的需求,就会抛出永久代的内存溢出异常。

大对象(须要大量连续内存的对象)例如很长的数组,会直接进入老年代,若是老年代没有足够的连续大空间来存放,则会进行 Full GC。

JVM系列:

深刻详解JVM 内存区域及内存溢出分析

JVM的判断对象是否已死和四种垃圾回收算法

JVM 配置经常使用参数和经常使用 GC 调优策略

7种JVM垃圾收集器特色,优劣势、及使用场景!

最后

后续会持续更新性能优化专题知识,写的很差的地方也但愿大牛能指点一下,你们以为不错能够点个赞在关注下我,刚刚入驻,之后还会分享更多文章!

分享免费学习资料
针对于还会准备免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)
为何某些人会一直比你优秀,是由于他自己就很优秀还一直在持续努力变得更优秀,而你是否是还在知足于现状心里在窃喜!

关注后台私信关键词【架构】便可免费获取资料,无套路关注公众号免费赠送,且公众号会不断更新技术干货及粉丝福利!

(部分资料以下)

相关文章
相关标签/搜索