JVM垃圾回收机制

什么是垃圾回收

java相较于c、c++语言的优势之一是自带垃圾回收器,垃圾回收是指不定时取堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收,垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,程序员唯一能做的激素hi通过调用System.gc方法来建议执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最重要的缺点。当然相当于它给程序员带来的巨大方便性,这个缺点是瑕不掩瑜的。

为什么需要垃圾回收

如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收。除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。

java中的四种引用类型

强引用

Object obj = new Object();//只要obj还指向Object对象,Object对象就不会被回收
obj = null;//手动设置null,可以通过System.gc方法进行回收处理

我们一般生命对象时虚拟机生成的引用,强引用环境下,垃圾回收需要严格判断当前对象是否被强引用,如果被强引用,就说明他不是垃圾,则不会被垃圾回收

软引用
软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用
弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次GC发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉该类对象。弱引用对象在回收时会被放入引用队列(ReferenceQueue)。

虚引用
虚引用被称为幽灵引用或幻象引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得对象实例。任何时候都可能被回收,一般用来跟踪对象被垃圾收集器回收的活动,起哨兵作用。必须和引用队列(ReferenceQueue)联合使用。

这些概念可能有点抽象,不过这四种引用具有不同的垃圾回收时机应该是很清楚的。我们会发现,引用的强度从强、软、弱、虚依次递减,越往后的引用所引用的对象越容易被垃圾回收。

垃圾识别机制

引用计数算法

引用计数算法是判断对象是否存活的算法之一:它给每一个对象加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能被使用的,即将被垃圾回收器回收。

缺点:
1.他需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
2.无法解决对象减互相循环引用的问题。即当两个对象循环引用时,引用计数器都为1,当对象周期结束后应该被回收却无法回收,造成内存泄漏。

public class GcTest {
    public static void main(String[] args) {       
      MyObject myObject_1 = new MyObject();       
      MyObject myObject_2 = new MyObject();                
      myObject_1.instance = myObject_2;        
      myObject_2.instance = myObject_1;
      myObject_1 = null;        
      myObject_2 = null;  
      System.gc();    
      }  
      // 对象循环引用,用引用计数算法时,无法回收这两个对象            

可达性分析算法

目前主流使用的都是可达性分析算法来判断对象是否存活。算法基本思路:以“GC Roots”作为对象的起点,从此节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

在这里插入图片描述
哪些对象可作为GC Roots?
虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(Native方法)引用的对象;
活跃线程的引用对象。

finalize()赋予对象重生

finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。

在可达性分析算法中被标记为不可达的对象,也不一定是一定会被回收,它还有第二次重生的机会。每一个对象在被回收之前要进行两次标记,一次是没有关联引用链会被标记一次,第二次是判断该对象是否覆盖finalize()方法,如果没有覆盖则真正的被定了“死刑”。

代码实现

public class FinalizeTest {
    public static FinalizeTest ft;
    /**
     * 用于判断对象是死亡还是存活
     */
    public static void judge(){
        if(ft == null){
            System.out.println("i am dead");
        }else{
            System.out.println("i am alive");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ft = new FinalizeTest();
 
        // 将引用指向null,那么对象就没有任何关联了
        ft = null;
        // 触发一次gc
        System.gc();
        // 因为Finalizer线程的优先级低,因此sleep 1秒后再看结果
        Thread.sleep(1000);
        //因为FinalizeTest对象覆盖了finalize方法,并在该方法中重新建立与引用的关联,所以对象会复活
        judge();
        //下面的代码和上面的一模一样,但是对象不会再复活了,因为finalize方法最多执行一次
        ft = null;
        System.gc();
        Thread.sleep(1000);
        judge();
    }
 
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("执行finalize方法");
        // 对象复活的关键:重新建立与引用的关联
        ft = this;
    }
}

流程图(finalize()存在时)
在这里插入图片描述

四种垃圾回收算法

确认完垃圾之后肯定要想办法回收垃圾。回收垃圾主要有下面四种方法:标记清除算法、标记整理算法、复制算法、分代收集算法。

标记清楚算法
算法思路:算法分为“标记”和“清理”两个步骤,首先标记处所有需要回收的对象,在标记完成后再统一回收所有被标记的对象。

缺陷:
1.标记和清理的两个过程效率都不高;
2.容易产生内存碎片,碎片空间太多可能导致无法存放大对象。

适用于存活对象占多数的情况。

在这里插入图片描述
标记整理算法
算法思路:标记过程和标记-清理算法一样,而后面的不一样,它是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
在这里插入图片描述
复制算法
算法思路:将可用内存划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完后,就将还存活的对象复制到另一块去,然后再把已使用过的内存空间一次清理掉。

缺陷:
可用内存缩小为了原来的一半
算法执行效率高,适用于存活对象占少数的情况。

在这里插入图片描述
分代收集算法
当前大多数垃圾收集都采用的分代收集算法,这种算法并没有什么新的思路,只是根据对象存活周期的不同将内存划分为几块,每一块使用不同的上述算法去收集。在jdk8以前分为三代:年轻代、老年代、永久代。在jdk8以后取消了永久代的说法,而是元空间取而代之。

在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代
老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须“标记-清除-压缩”算法进行回收。

Minor GC和Full GC区别
新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。
在这里插入图片描述
年轻代分为伊甸园区和幸存区,幸存区分为from和to区。每次执行GC操作,会将伊甸园区和from区存活的对象放到to区(如果to区不够,则直接进入老年代),然后将from区和to区的职责交换(from区变为to区,to区变为from区,谁空谁是to区)。

在这里插入图片描述
新生代中的对象98%都是朝生夕死的,因此把新生代分为较大的一块Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor(此处Survivor区也叫From区,另一块空的未使用的空间叫To区,From和To区是会交换的,保证空的总是To区)。

当Eden区没有足够的空间分配时,会进行一次Minor GC,Eden区大部分对象都被回收,而Eden区和From区存活的对象会放入到To区,然后From和To区进行交换。(如果To区空间不够,直接进入老年代)

以下几种情况会进入老年代。

1、 大对象
大对象就是指需要大量连续内存空间的对象,最典型的就是那种很长的字符串和数组。大对象会直接进入到老年代,这样做的目的主要是为了避免新生代发生大量的内存复制(大对象的复制成本较高)。

2、长期存活的对象
虚拟机给每个对象都定义了一个对象年龄计数器。每当进行一次Minor GC,年龄就增加1岁,当年龄超过一定值时(默认是15,可以通过参数配置),就进入到老年代。

3、动态对象年龄判断
虚拟机并不要求对象年龄一定要到达15岁才进入到老年代。如果Survivor空间中有某年龄相同的所有对象大小总和大于Survivor空间的一半,则年龄大于等于该年龄的对象就会直接进入老年代。

原文链接

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值