深入理解Java虚拟机之垃圾收集算法

来源:互联网 发布:生辰八字宝宝起名软件 编辑:程序博客网 时间:2024/05/21 01:51

垃圾回收区域:

       Java内存运行时,运行区域包括程序计数器、虚拟机栈、本地方法栈、堆、方法区,其中程序计数器、虚拟机栈、本地方法栈是线程私有的,随线程而生,随线程而灭。栈中的栈帧随着方法的进入及退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存在类结构确定下来时就已知。因此这几个区域的内存分配和回收都具备确定性,在这几个区域就不必过多考虑回收的问题,因为方法结束时或者线程结束时,内存自然就跟着回收了。

       堆与方法区不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序运行期间才知道创建哪些对象,这部分的内存分配和回收是动态的,垃圾收集器所关注的也是这部分内存。

对象存活判断:

       垃圾收集器回收对象,首先要判断对象是否“存活”,即被其它途径使用的对象。一般的算法是引用计数法,给对象添加一个引用计数器,当有地方引用这个对象时,计数器就加一,当引用失效时,计数器减一,任何时候计数器为零的对象不可能再被使用的。引用计数算法简单,但其无法解决互相引循环用的问题。即两个对象不可能再被访问,但它们相互引用对方,导致引用计数器不为零,无法被垃圾收集器回收。


       在主流的商用程序语言中的主流实现中,都是通过可达性分析来判定对象是否存活。这个算法是通过一系列称为“GC ROOT”的作为起点,从这些起始点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOT之间不存在引用链时,则此对象不可用。一个不可用的对象并非“非死不可”,这时候处于“缓刑”阶段,要真正宣告死要经历两次标记阶段。

       如果一个对象和GC ROOT之间不存在引用链,则将会被标记一次并且进行一次筛选,筛选的条件是此对象是否有finalize()方法,当对象没有覆盖finalize()方法时或者finalize()已经被调用过,虚拟机将这两种情况都视为“没有必要执行”。

       当被虚拟机认为有必要执行finalize()时,那么对象会被放入F-Queue队列之中,并且之后由虚拟机自动建立的F-Queue线程执行它。GC会对F-Queue中的对象进行第二次标记。finalize()是对象逃离“死亡”的最后一次机会,只需将自己与引用链上的任何对象建立关联,即可拯救自己。如果第二次标记后没有拯救成功,那基本就真的被回收了。

一般作为GC ROOT 的对象有下面几种:

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(一般说Native)引用的对象。

引用类型:

引用可分为强引用、软应用、弱引用、虚引用。
       强引用:在程序中普遍存在,类似“Object obj=new Object()”这类的引用,只要强引用还存在,一般垃圾收集器永远不会回收掉被引用的对象。
       软引用:软应用描述一些有用但并非必须的对象,对于软引用关联的对象,在系统将要发生内存溢出时,将会把这些对象列入回收范围之中进行第二次回收,如果回收后还没有足够内存,才会发生内存溢出异常。
       弱引用:比软引用强度低,被弱引用关联的对象在,无论内存是否足够,都会被垃圾收集器回收内存。
       虚引用:是最弱的引用关系,一个对象是否有虚引用的存在完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。对对象设置一个虚拟引用唯一目的是能在这个对象被垃圾收集器回收时收到一个系统通知。


垃圾收集算法:

标记-清楚算法:
       最基础的即是“标记-清楚”算法,分为“标记”和“清楚”两个阶段,首先标记需要清除的对象,在标记完成后统一回收所有被标记的对象。
       缺点是“标记”和“清除”效率都不高,另一个问题是“标记清除”容易产生空间碎片,空间碎片太多容易导致以后的在需要分配较大对象时,无法找到连续内存而不得不提前触发另一次垃圾收集动作。

复制算法:
       将空间划分为大小相等的两块,每次只使用一块,当空间快用完的时候,将这一块存活的对象复制到另一块内存中然后对使用过的那块内存进行空间清理。
       优点:不必考虑空间碎片问题,实现简单高效。
       缺点:使用的内存为原来内存的一半,代价高。
       现在虚拟机一般采用这种算法实现新生代,新生代中98%的对象一般是“朝生夕死”,所以不必将空间划分为1:1,而是分为一块较大的Eden空间和两块较小的Survivor空间,(HotSpot虚拟机默认的Eden与Survivor空间大小比值为8:1)每次使用Eden和一块Survivor空间,当回收时,将Eden与正在使用的Survivor空间存活的对象复制到另一个空闲的Survivor空间,最后清理掉Eden及刚才用过的Survivor空间。

标记-整理算法:
      复制收集算法在对象存活率较高时,效率将会变低,所以在老年代中一般不选用这种算法。
      标记-清楚算法,也是先标记,再让对象向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法:
       根据对象存活周期,将内存划分为几块,一般把Java堆分为新生代和老年代,新生代对象存活率较低,使用复制算法,而老年代中,对象存活率较高,就必须使用“标记-清楚”或者“标记-整理”算法来进行回收。

阅读全文
0 0
原创粉丝点击