您的位置:首页 > 编程语言 > Java开发

JVM垃圾收集器

2017-03-12 20:25 162 查看
垃圾标记策略

引用计数算法:给对象添加一个引用计数器,当有地方引用时,计数器就加一;当引用消失时,计数器减一。当引用为0时,对象就会被回收。引用计数算法实现简单,判定效率很高,在大多数情况下是一个不错的算法,但这种算法有一种缺陷,如下面的代码,当两个对象相互引用时,并且这两个引用不可能被访问时,他们的引用数都不为0。但是,运行程序会发现,它们被回收了,这是因为jvm还使用了另一种算法–可达性分析算法。

public class ReferenceCountGC {

private Object instance = null;

public static void main(String[] args) {
ReferenceCountGC obj1 = new ReferenceCountGC();
ReferenceCountGC obj2 = new ReferenceCountGC();
obj1.instance = obj2;
obj2.instance = obj1;
obj1 = null;
obj2 = null;
System.gc();
}


}

可达性分析算法:这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起点,从这个节点开始向下搜索,搜素所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,则证明此对象不可用,是需要被回收的。

java中的四种引用

强引用:如果一个对象具有强引用,那就 类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用:如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

弱引用:如果一个对象只具有弱引用,那就类似于可有可物的生活用品。 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用:又称为幽灵引用或幻影引用,,虚引用既不会影响对象的生命周期,也无法通过虚引用来获取对象实例,仅用于在发生GC时接收一个系统通知。

垃圾收集算法

虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

年轻代:

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

年老代:

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

持久代:

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

标记-清除算法

分为两个阶段:标记和清除。首先标记出所用需要被回收的对象,在标记完成后统一回收所有被标记的对象。这种算法主要有两个不足:一个是效率问题,标记和清除的效率都不高;另一个是空间问题,标记清除后,会产生大量不连续的内存碎片,内存碎片太多会导致后面程序运行中需要为较大的对象分配空间是,无法找到足够的连续空间,而不得不提前触发一次GC。

复制算法

为了解决上面的问题就提出复制算法,它是将内存分成大小相等的两块,每次只使用其中一块,当这一块内存用完了,就将还存活的对象复制到另一块内存上,然后然后把使用过的空间一次清理掉。这个算法解决了内存碎片的问题,但是它将能使用的内存缩小了一半。

在jvm中就用到了这一算法来回收新生代。因为新生代中对象的存活率比较低,就把新生代的内存分成一块较大的Eden区和两块较小的Survivor区,每次只使用Eden和其中一块Survivor区,当回收的时候,把Eden区和Survivor区中还存活的对象复制到那块未使用的Survivor区里,最后清除Eden区和用过的Survivor区。我们不能保证每次回收时,survivor区都够用,当survivor区不够用时,需要依赖其他内存(这里只老年代)进行分配担保。

标记-整理算法

在存活率较高的时,复制算法的效率就会变低。这个时候就应该选用标记-整理算法。标记-整理算法的标记过程和标记-清除算法一样,但后续步骤不是直接清除可回收的对象,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。老年代的垃圾回收就用了这种算法。

分代收集算法

根据各个年代的特点。来采用最合适的算法。在新生代中,每次都有大量的对象死去,所以采用复制算法,每次只需付出较小的成本,来复制那些需要存活下来的对象,就可以完成回收。而老年代中因为存活的对象较多,没有额外空间来进行分配担保,就必须使用标记清理或者标记整理来回收。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jvm 垃圾回收 java