最新消息:从今天开始,做一个有好习惯的人。

垃圾回收(GC)机制算法

java体系 迷路的老鼠 4734浏览 2评论

Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。

误区:不可达的对象并不会马上就会被直接回收,而是至少要经过两次标记的过程。

垃圾回收简要过程

第一次被标记过的对象,会检查该对象是否重写了finalize()方法。如果重写了该方法,则将其放入一个F-Query队列中,否则,直接将对象加入“即将回收”集合。在第二次标记之前,F-Query队列中的所有对象会逐个执行finalize()方法,但是不保证该队列中所有对象的finalize()方法都能被执行,这是因为JVM创建一个低优先级的线程去运行此队列中的方法,很可能在没有遍历完之前,就已经被剥夺了运行的权利。那么运行finalize()方法的意义何在呢?这是对象避免自己被清理的最后手段:如果在执行finalize()方法的过程中,使得此对象重新与GC Roots引用链相连,则会在第二次标记过程中将此对象从F-Query队列中清除,避免在这次回收中被清除,恢复成了一个“正常”的对象。但显然这种好事不能无限的发生,对于曾经执行过一次finalize()的对象来说,之后如果再被标记,则不会再执行finalize()方法,只能等待被清除的命运。

之后,GC将对F-Queue中的对象进行第二次小规模的标记,将队列中重新与GC Roots引用链恢复连接的对象清除出“即将回收”集合。所有此集合中的内容将被回收。

finalize()方法最多只会被执行一次。

finalize()的作用

Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

垃圾回收的算法包括引用计数、可达性算法、标记清除、复制、标记整理、分代收集等,其中java新生代使用的算法是复制算法,老年代使用的是标记压缩。

其中,引用计数和可达性两种算法是对象判断是否可回收的基础算法,标记清除、复制、标记整理是回收过程算法,分代收集是回收模型。

引用计数算法

在对象中添加一个计数器,引用一次计数器就加一,引用失效就减一,计数器为0时表示没有引用,就会被清除掉。

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.而且每次加减非常浪费内存。

可达性分析算法

在主流的商用语言(Java、C#)中都使用可达性分析(Reachability Analysis)来判定对象是否存活的。通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

标记清除算法

标记就是根据特定的算法(如:引用计数算法,可达性分析算法等,一般采用可达性算法)标出内存中哪些对象可以回收,哪些对象还要继续用。标记指示回收,那就直接收掉;标记指示对象还能用,那就原地不动留下。

优点:无特别明显的优点,一般不采用

缺点:效率比较低、清除后产生大量内存碎片

复制算法

将内存分为几个区域,每次GC时将存活的数据复制到另外一个区域,然后清除当前区域。保证内存的连续性。

目前商用的虚拟机新生代中都是采用复制算法,将内容分为伊甸园区和两个幸存区。对象初始化时放入伊甸园区和某一个幸存者区,GC后一起复制到另外一个幸存者区,由于幸存者区一般来说比较小,幸存者区如果空间不够,就会进入老年代。

优点:不会产生内存碎片,整体效率比较高

缺点:占用比较多的内存

标记整理算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

复制算法的高效建立在存活对象少,垃圾对象多的前提下。这种情况在新生代比较容易发生,在老年代更常见的情况是大部分都是存活对象。标记整理算法,是一种老年代的回收算法,从根节点对所有的对象做一次标记,然后将所有存活的对象压缩到内存的另外一端,在清楚界边界外所有的空间。这种方法不产生碎片,又不需要2块相同的内存空间。

优点:不需要复制算法需要额外的内容空间,同样实现无内存碎片

分代收集算法

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到老年代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在老年代中创建。
对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时很短,而老年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC.

为什么老年代使用标记压缩、新生代使用复制算法?

前面提到了,新生代的垃圾对象比较多只需要复制较少的对象就可以满足需求,老年代恰好相反,存活率比较高,且老年代的GC频率远远低于新生代,使用标记整理就能满足需求

垃圾回收时的停顿现象

垃圾回收的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以更高效的执行,大部分情况下,会要求系统进如一个停顿的状态。停顿的目的是为了终止所有的应用线程,只有这样的系统才不会有新垃圾的产生。同时停顿保证了系统状态在某一个瞬间的一致性,也有利于更好的标记垃圾对象。因此在垃圾回收时,都会产生应用程序的停顿。

什么时候发生full gc

full gc只会发生在老年代,当新生代转移到老年代时,老年代内存不足,就会触发full gc,full gc是针对整个堆来说的,会触发一次新生代和老年代的整体GC。full gc的危害性就在这里。

转载请注明:迷路的老鼠 » 垃圾回收(GC)机制算法

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (2)

  1. 为努力拼搏的人儿点赞👏
    wa5年前 (2019-10-10)回复
    • 难得闲一点
      迷路的老鼠5年前 (2019-10-11)回复