JVM-JVM垃圾回收算法

1. 对象生死判定

    如何判断Java中一个对象应该“存活”还是“死去”,这是垃圾回收器要做的第一件事。

1)引用计数法

    Java堆中每个具体对象(不是引用)都有一个引用计数器。当一个对象被创建并初始化赋值后,该变量计数设置为1。每当有一个地方引用它时,计数器值就加1。当引用失效时,即一个对象的某个引用超过了生命周期(出作用域后)或者被设置为一个新值时,计数器值就减1。任何引用计数为0的对象可以被当作垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1。

  • 优点

        引用计数收集器执行简单,判定效率高,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。

  • 缺点

        难以检测出对象之间的循环引用。同时,引用计数器增加了程序执行的开销。所以Java语言并没有选择这种算法进行垃圾回收。

2)可达性分析法

    可达性分析算法也叫根搜索算法,通过一系列的称为GC Roots的对象作为起点,然后向下搜索。搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,即该对象不可达,也就说明此对象是不可用的。

如下图所示:Object5、Object6、Object7虽然互有关联虽然互有关联,但他们到GC Roots是不可达的,因此也会被判定为可回收的对象。

41afe8c787538df10e869921b4f71abe925.jpg

GC根对象

    在Java中,可作为GC Roots的对象包括以下4种:

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

    JVM中用到的所有现代GC算法在回收前都会先找出所有仍存活的对象。可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图。下图展示的JVM中的内存布局可以用来很好地阐释这一概念:

de266ec234fcfd91b40b9eb00af007ea311.jpg

2. 对象引用分类

1)强引用(Strong Reference)

    在代码中普遍存在,类似于 User user = new User()这类引用,只要强引用还在,垃圾收集器永远不会回收被引用的对象。

2)软引用(Soft Reference)

    有用但并非必需的对象,可用SoftReference类来实现软引用。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

3)弱引用(Weak Reference)

    非必须的对象,但它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,JDK提供了WeakReference类来实现弱引用。无论当前内存是否足够,用软引用相关联的对象都会被回收掉。

4)虚引用(Phantom Reference)

    虚引用也称为幽灵引用或幻影引用,是最弱的一种引用关系,JDK提供了PhantomReference类来实现虚引用。作为一个对象设置虚引用的唯一目的是:能在这个对象在垃圾回收器回收时收到一个系统通知。

3 finalize() 二次标记

    一个对象是否在垃圾回收器在GC时回收,至少要经历两次标记过程。

第一次标记过程,通过可达性分析算法分析对象是否与GC Roots可达。经过一次标记,并且被筛选为不可达的对象会进行第二次标记。

第二次标记过程,判断不可达对象是否有必要执行finalize方法。执行条件是当前对象的finalize方法被重写,并且还未被系统调用过。如果允许执行那么这个对象将会被放到一个叫F-Query的队列中,等待被执行。

4 垃圾回收算法

1)标记-清除算法

    标记-清楚算法对根集合进行扫描,对存活的对象进行标记。标记完成后,再对整个空间内未被标记的对象扫描,进行回收。

  • 优点

        实现简单,不需要对对象进行移动。

  • 缺点

        标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。

2)复制算法

    将内存区域划分成相同的两个内存块。每次仅使用一半的空间,JVM生成的新对象放在一半空间中。当一半空间用完时进行GC,把可到达对象复制到另一半空间,然后把使用过的内存空间一次清理掉。这种收集算法解决了标记清除算法存在的效率问题。

  • 优点

        按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。

  • 缺点

        可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。

3)标记-整理算法

    标记-整理算法采用标记-清除法一样的方式进行对象的标记,但后续不直接对可回收对象进行清理,而是将所有的存活对象往一端空闲空间移动,然后清理掉端边界以外的内存空间。

  • 优点

        解决了标记-清理算法存在的内存碎片问题。

  • 缺点

        仍需要进行局部对象移动,一定程度上降低了效率。

4)分代收集算法

    当前商业虚拟机都采用分代收集的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期将内存划分为几块。一般包括年轻代、老年代和永久代,如图所示:

d2bbd3a09ae17800595af32bf6bb5a34008.jpg

新生代(Young Generation)

    绝大多数最新被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可达,所以很多对象被创建在新生代,然后消失。对象从这个区域消失的过程,我们称之为Mirror GC。

    新生代中存在一个Eden区和两个Survivor区。新对象会首先分配在Eden中(如果新对象过大,会首先分配在老年代中)。在GC中,Eden对象会被移到Survivor中,直至对象满足一定的年纪(定义为熬过GC的次数),会被移动到老年代。

    可以设置新生代和老年代的相对大小。这种方式的优点是新生代大小会随着整个堆大小动态扩展。参数 -XX:NewRatio 设置老年代与新生代的比例。例如

-XX:NewRatio=8 表示 老年代/新生代=8:1;老年代占堆大小的7/8,新生代占堆大小的1/8(默认即是1/8)。举个配置的例子:

-XX:NewSize=64m -XX:MaxNewSize=1024m -XX:NewRatio=8

老年代(Old Generation)

    对象没有变得不可达,并且从新生代中存活了下来,会被拷贝到这里。其所占用的空间要比新生代多。也正由于其相对较大的空间,发生在老年代上的GC要比新生代的要少得多。对象从老年代中消失的过程,可以称之为Major GC/Full GC。

永久代

    像一些类的层级信息,方法数据和方法信息(如字节码、栈和变量大小),运行时常量池(JDK7之后移出永久代),已确定的符号引用和虚方法表等等。他们几乎都是静态的并且很少被卸载和回收,在JDK8之前的HotSpot虚拟机中,类的这些“永久的”数据存放在一个叫做永久代的区域。

    永久代一段连续的内存空间,我们在JVM启动之前,可以通过设置-XX:MaxPermSize的值来控制永久代的大小。但是JDK8之后取消了永久代,这些元数据被移到了一个与堆不相连的成为元空间(Metaspace)的本地内存区域。

小结

    JDK8堆内存一般是划分为年轻代和老年代,不同年代根据自身特性,采用不同的垃圾收集算法。

  • 对于新生代,每次GC时都有大量的对象死亡,只有少量的对象存活。考虑到复制成本低,适合采用复制算法。因此有了From Survivor和To Survivor区域。
  •  对于老年代,因为对象存活率高,没有额外的内存空间对他进行担保,因而适合采用标记-清理算法和标记-整理算法进行回收。

转载于:https://my.oschina.net/mobinchao/blog/2988293

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值