3. Java的内存回收

余生颓废 提交于 2020-01-12 13:57:44

一、判断对象死亡的方法

1. 引用计数法

       给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器就加1;当引用失效时,计数器就减1;当计数器为0时,表示该对象就不可能再被使用了。

       它的优点为实现简单,判断效率也很高。但是它存在一个问题:它无法解决对象之间的相互循环引用的问题。   

2. 可达性分析算法   

       通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何的引用链相连(用图论来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

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

        若这个对象被判定为有必要执行finalize()方法,这个对象将会被放置在一个叫做F-Queue的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。这个所谓的“执行”是指虚拟机会触发这个方法,但是并不承诺会等待它运行结束。这样做的原因是防止一个对象在finalize()方法中执行缓慢或者发生了死循环,导致F-Queue队列中其他对象永久处于等待。甚至导致整个内存回收系统崩溃。

                                          

在Java中,可作为GC Roots的对象包括:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

二、Java中的引用

在JDK1.2 之后,java将引用分为了以下几种:

  • 强应用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)

1. 强引用

     类似“Object obj = new Object()”这类的引用,只要强引用还在,垃圾收集器就永远不会回收被引用的对象。Java宁愿抛出OOM异常也不会回收这种引用的对象。

2. 软引用

      它是用来描述非必要对象的。对于软引用的对象,在要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。若这次回收还是没有足够的内存,就会抛出内存溢出异常。

3. 弱引用

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

4. 虚引用

       它也被称为幽灵引用或幻像引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生命周期构成影响。也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾回收器回收时收到一个系统通知。

三、方法区的回收

        在Java虚拟机规范中说过可以不要求虚拟机在方法区中实现垃圾收集,且在方法区中进行垃圾回收的“性价比”较低。

        在永久代(在1.8中被移除换成了元空间)中的垃圾回收主要回收两个部分:废弃常量和无用的类。废弃常量的回收与回收Java堆中的对象非常类似。

判断一个类是否是“无用的类”需要满足下面三个条件:

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

四、垃圾收集算法

1. 标记-清除算法

       它是最基础的收集算法。它分为“标记”和“清除”两个阶段。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。缺点主要有两个:一是效率问题,标记和清除两个过程的效率都不高;另一个就是标记清除后会产生大量的内存碎片,太多的话会导致以后再程序运行过程中需要分配较大对象时。因无法找到足够的连续空间而不得不提前出发一次垃圾收集动作。

                                           

2. 复制算法 

       它将可用内存按照容量划分为大小相等的两块,每次只使用一块。当这一块内存用完时,就将还存活的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。优点是效率高,且不会产生内存碎片。缺点是每次只使用一半的内存空间,内存的利用率较低。现代的商业虚拟机都采用这种方法来回收新生代。且不是按照1:1来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和Survivor空间。当回收时,将Eden和Survivor中还存活着的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。HotSpot默认Eden和Survivor的大小比例为8:1。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。

                                             

3. 标记-整理算法

       与标记-清除算法相似,不同的是不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

                                         

4. 分代回收算法

       当前的商业虚拟机的垃圾收集都采用了此方法。它根据对象的存活周期的不同将内存划分为几块。一般讲Java堆分为新生代和老年代,根据各个年代的特点采用不同的收集算法。在新生代中采用复制回收算法,老年代则采用标记-整理或标记-清除算法.来进行垃圾回收。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!