大家好,欢迎来到IT知识分享网。
之前发布过解锁JVM成神之路(一) ,里面介绍了运行时数据区,类加载子系统以及双亲委派机制。现在说学习gc算法。
如何判断对象是否可以被回收
在jvm的堆内存中,存放着许多new出来的对象,要对这些对象进行回收,第一步永远是判断这些对象是否可以被回收,那么如何判断呢?
引入计数法
这种方式是给对象增加一个计数器,每当有地方在引用这个对象时,那么计数器就加1,而引用失效后,计数器就会减1。任何时候,计数器为0时,那么这个对象就能被回收。这种方式简单而且效率高,但是现在主流的jvm不再使用这种方式。因为,它无法解决循环引用的问题(对象之间循环引用)就。通过下面代码所示:
public class GCDemo1 { Object object = null; public static void main(String[] args) { GCDemo1 a = new GCDemo1(); GCDemo1 b = new GCDemo1(); a.object = b; b.object = a; a = null; b = null; } }
对象a和对象b相互引用着对方,这两个对象再无任何引用。但是他们因为相互引用着导致他们的引用计数器不为0,于是GC无法回收他们。
可达性分析算法
现在主流的jvm都是采用这个方式,它基本思想就是从一个叫做”GC Roots”的对象作为起点,从这个起点开始向下搜索,走过的路,我们称之为“引用链”。当一个对象到GC Roots没有任何的引用链时,则此对象不可用,我们称之为对象不可达,那么就可以被回收了。
但是这里要注意的是,一个对象在被回收之前,至少要经过2次标记不可达。如果一个对象和GC Roots没有任何的引用链时,则这个对象第一次被标记为可收回。然后在做一次筛选,这个过程是:如果这个对象没有覆盖finalize()或者finalize()方法已经被虚拟机调用过,那么这个对象就将被放到一个叫做F-Queue的队列中,队列中对象的finalize()方法将由一个虚拟机自动建立低优先级的Finalize线程去执行。
finalize()方法执行的过程是对象逃脱被回收的最后机会,如果对象在finalize()中与GC Roots有引用链,那么这个对象就会被移除队列。因此,我们在开发的过程中,不建议覆盖finalize()方法,或者在finalize()做一些清理资源的工作。因为它的不确定性,而且运行代价很高,完全可以使用try-finally去代替使用。
如何判断一个常量是废除的常量
运行时常量池主要是回收废弃的常量。假如常量池中有“abc”字符串,当没有任何的String对象引用它时,那么“abc”就是废弃的常量,在发生内存回收时,如果有必要,那么“abc”就会被清理出常量池。
如何判断一个类是无用的类
满足以下三个条件:
- 该类的所有实例已经被回收。jvm堆中没有任何它的实例
- 加载该类的ClassLoader已经被回收
- 该类的Class对象没有任何的地方引用。也就是任何地方都无法通过发射来访问该类
满足以上三个条件,那么就可以对其进行回收。这里说的是可以,并不是和对象一样必然被回收。
垃圾回收算法
大致有四个算法:标记清除算法,复制算法,标记整理算法,分代回收算法
标记清除算法
分为两个步骤,先标记,后清除。这种最简单,但是产生内存碎片,会导致大对象无法找到可以利用的内存空间
复制算法
思路就是将内存容量分为两个部分,先使用一部分,当这部分没有空间使用时,将存活的对象复制移到另一部分上,然后再对第一部分进行清理。这种方式也简单,但是最大的缺点就是内存只能利用一半,效率有点低。
标记整理算法
结合以上两种算法而得出,标记阶段跟标记清理算法的一致,只是标记后不清理对象,而是将存活的对象向内存的另一端移动,然后回收这一端以外的内
分代回收算法
垃圾收集器基本都采用这种算法,思路就是根据对象的存活周期不同将内存划分为几块区域,就可以根据它们的特点分别对这些区域进行回收。
一般情况下,gc最活跃的地方就是在堆中。我们可以根据堆的特点将其分为新生代和老年代。新生代的特点就是每次垃圾回收都有大量的内存需要回收,而老年代则只有少部分需要回收。因此可以根据这些特点进行选择不同的算法。
新生代与复制算法
因为新生代每次都要回收大量的对象,所以可以选择复制算法,只要付出少量对象的复制成本就可以完成每次垃圾收集。所以在新生代中又分为三个区域,一个比较大的区域为Eden Space,以及两个比较小的区域Survivor 空间分别是:From Space,To Space。每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中
老年代与标记清理(整理)算法
老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保。因此选择标记清理算法或者标记整理算法:
- jvm的方法区我们称之为永生代(Permanet Generation),它用来存储 class 类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。
- 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放对象的那一块),少数情况会直接分配到老生代。
- 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,EdenSpace 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From
- Space 进行清理。
- 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
- 在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。
- 当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被移到老生代中。
总结:
- 在jvm中判断对象是否可以被收回使用的是可达性分析算法;
- 一个对象要被垃圾收集器回收至少要标记2次;
- 垃圾回收算法有:标记清理算法,复制算法,标记整理算法,分代回收算法;
本人水平有限,难免有错误或遗漏之处,望大家指正和谅解,提出宝贵意见,愿与之交流。公众号:小白学java
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/55663.html