Java垃圾回收机制(3)- GC算法

来源:互联网 发布:单片机与modbus 编辑:程序博客网 时间:2024/06/06 09:05

Java中,GC的对象主要是堆空间和永久区,很多人都认为Java的GC使用的是引用计数法,其实这是错误的,Java可以说从来都没有用过这个引用计数算法 !这是一个非常古老的算法了。

引用计数法,它的一个基本思想

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。就可以回收了。如图有一个根对象,和一个可达的对象:

这里写图片描述

Java为什么不用他呢,因为引用计数法有很多缺点 :
1. 性能,每次引用和去引用都要加减
2. 循环引用问题

这里写图片描述

对象1没办法回收,但是确实没有用了。

现代Java的垃圾回收使用的基本的算法思想是标记-清除算法

标记-清除算法是现代垃圾回收算法的思想基础。将垃圾回收分为两个阶段:标记阶段和清除阶段。
一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象(从GC ROOT开始标记引用链——又叫可达性算法 )。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。这样就不怕循环问题了。

PS:Java中可以作为GC ROOT的对象有:
1. 静态变量引用的对象
2. 常量引用的对象
3. 本地方法栈(JNI)引用的对象
4. Java栈中引用的对象
如图,从根节点能到达的都是不能回收的,是被引用的。标记下。而这种算法的缺点就是容易出现内存碎片。利用率不高。

这里写图片描述

要知道,现代的Java虚拟机都是使用的分代回收的设计 ,比如在标记-清除算法的基础上做了一些优化的——标记-压缩算法, 适合用于存活对象较多的场合,如老年代。

和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。有效解决内存碎片问题。

这里写图片描述

还有一个算法,针对新生代的回收,叫复制算法

和标记-清除算法相比,复制算法是一种相对高效的回收方法,但是不适用于存活对象较多的场合如老年代,使用在新生代,原理是将原有的内存空间分为两块,两块空间完全相同,每次只用一块 ,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。同样也没有内存碎片产生。

复制算法的缺点是内存的浪费 ,因为每次只是使用了一般的空间, 而大多数存活对象都在老年代,故复制算法不用在老年代,老年代是Java堆的空间的担保地区。复制算法主要用在新生代。在垃圾回收的时候,大对象直接从新生代进入了老年代存放,大对象一般不使用复制算法,因为一是太大,复制效率低,二是过多的大对象,会使得小对象复制的时候无地方存放。还有被长期引用的对象也放在了老年代。

Java的垃圾回收机制使用的是分代的思想。依据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代。根据不同代的特点,选取合适的收集算法。 少量对象存活(新生代,朝生夕死的特性),适合复制算法, 大量对象存活(老年代,生命周期很长,甚至和应用程序存放时间一样),适合标记清理或者标记压缩算法。

原创粉丝点击