JVM垃圾回收机制(超级无敌认真好用,万字收藏篇!!!!)

JVM垃圾回收机制

1 判断对象是否存活的算法

堆内存进行垃圾回收,需要判断哪些对象已死

1.1 引用计数器算法

  • 为对象添加一个引用计数器,当有一个地方引用该对象时,计数器+1,当引用失效时,计数器-1,当计数器为0,说明该对象已死;

  • 引用计数器算法实现简单、效率高,适用于大多数适用场景,但是主流的JVM均没有采用了这种算法,原因在于它很难解决对象之间的相互循环引用情况,容易形成僵尸对象,发生内存泄漏;

    在这里插入图片描述

1.2 可达性分析算法

在主流的商用虚拟机中都是通过可达性分析算法来判断对象是否存活,这个算法核 心思想就是通过一个“GCRoots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到“GC Roots”没有任何引用链相连,则证明该对象不再被引用。

GCRoots:外部变量,他可以是(本地方法栈,虚拟机栈,方法区)任意变量;

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象。(可以理解为:引用栈帧中的局部变量表的所有对象)
  • 方法区中静态属性引用的对象(可以理解为:引用方法区该静态属性的所有对象)
  • 方法区中常量引用的对象(可以理解为:引用方法区中常量的所有对象)
  • ​ 本地方法栈中(Native 方法)引用的对象(可以理解为:引用 Native 方法的所有对象)

在这里插入图片描述

2 对象的四种引用方式

无论是通过引用计数器还是通过可达性分析判断对象是否存活都跟引用有关;

在 JDK1.2 以前,Java 中引用的定义很传统: 如果引用类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。这种定义有些狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态。

public class A(){
    
    
   
    b.say();
    b=null;
    //当我们想再次使用该对象需要重新引用,JDK1.2之前只有该引用方法,但是该引用方式不能满足我们实际需求
    B b1 =new B();
}
class B(){
    
    
    
}

我们希望能描述这一类对象: 当内存空间还足够时,则能保存在内存中;如果内存空间 在进行垃圾回收后还是非常紧张,则可以回收这些对象。很多系统中的缓存对象都符合这样的场景。

在 JDK1.2 之后,Java 对引用的概念做了扩充,将引用分为强引用软引用弱引用虚引用四种,这四种引用的强度依次递减。

2.1 强引用

B b =new B();

  • 强引用只有两种状态,正在被引用和未被引用;

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

2.2 软引用

  • 软引用描述一些,还有用,但非必要的对象,如果内存空间充足,垃圾回收器不会回收,如果空间不充足,就会回收这些对象的内存。

  • 软引用可以实现一些内存高度敏感的内存

    JDK1.2使用java.lang.ref.SoftReference来实现软引用

    class A{
          
          
        public void say(){
          
          
            System.out.println("im a say()");
        }
        @Override
        protected void finalize() throws Throwable {
          
          
            System.out.println("A类的对象被垃圾回收器清理");
        }
    }
    
    public class SoftReferenceDemo {
          
          
        public static void main(String[] args) {
          
          
            A a =new A();//强引用
            System.out.println(a);//查看强引用地址
            a.say();//强引用调用
            //将a对象设置为软引用
            SoftReference<A> softReference =new SoftReference<A>(a);
            System.out.println(a);//查看软引用地址
            a=null;
            System.gc();
            softReference.get().say();//软引用未被回收
            //从软引用中获取a对象,赋值给a,对象由回到强引用状态
            a = softReference.get();
            a =null;
            System.gc();
            System.out.println(a);
        }
    }
    

    运行控制台输出

    com.jiazhong.jvm.引用.A@4554617c   //查看强引用地址
    im a say()						  //强引用调用
    com.jiazhong.jvm.引用.A@4554617c	//查看软引用地址
    im a say()						 //软引用未被回收
    null							
    

    软引用为空后,内存不会回收,直至内存不足

2.3 弱引用

  • 弱引用与软引用的区别在于:具有弱引用的对象拥有更短暂的生命周期,它仅仅能够生存到垃圾回收之前,当垃圾收集时,无论内存是否足够,弱引用的对象都要被回收

    JDK1.2 以后使用 java.lang.ref.WeakReference类来实现弱引用

    public class WeakReferenceDemo {
          
          
        public static void main(String[] args) {
          
          
            A a =new A();
            System.out.println(a);  //强引用地址
            a.say();                //强引用调用say()
            WeakReference<A> weakReference =new WeakReference<A>(a);
            System.out.println(a);  //弱引用地址
            a = null;
            weakReference.get().say();//弱引用调用say()
            System.gc();
            weakReference.get().say();//弱引用被回收了
        }
    }
    
    com.jiazhong.jvm.引用.A@4554617c	//强引用地址
    im a say()						 //强引用调用say()	
    com.jiazhong.jvm.引用.A@4554617c	//弱引用地址
    im a say()						 //弱引用调用say()
    A类的对象被垃圾回收器清理            //弱引用被回收了,调用say()空指针异常
    Exception in thread "main" java.lang.NullPointerException
    	at com.jiazhong.jvm.引用.WeakReferenceDemo.main(WeakReferenceDemo.java:15)
    

    弱引用,在内存回收后,就会被回收了

2.4 虚引用

  • “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。

    如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

    也无法通过虚引用获得一个实例对象;

    作用:

    • 使用虚引用的目的就是可以作为对象清理的通知来使用,当该对象被清理后,会将该对象放入其第二个参数的引用队列中,如果队列有改对象,表名对象被清理。

    • 虚引用是一个对象被清理的通知机制,他是对象被清理后的一种通知,接到该通知后,对象清理后可以做一下其他清理工作。

    • 虚引用无法同finalize()方法一起使用,如果使用finalize()清理则不能使用虚引用的通知机制,如果使用虚引用的通知则不能使用finalize()清理

    如果持有虚引用的对象重写了 finalize()方法则表示在对象被垃圾回收器清理前要做清理工作,此时虚引用的对象直接被清理不进入引用队列

    JDK1.2 以后使用 java.lang.ref.ReferenceQueue 类来实现引用队列

    public class PhantomReferenceDemo {
          
          
        public static void main(String[] args) {
          
          
            A a =new A();
            a.say();
            /**
             * 设置虚引用
             * 参数1:虚引用的对象
             * 参数2:引用队列
             */
            ReferenceQueue<? super A> referenceQueue =new ReferenceQueue<>();
            PhantomReference<A>  phantomReference =new PhantomReference<A>(a,referenceQueue);
            a = null;
    //        System.out.println(phantomReference.get());//无法获取
            System.gc();
            System.out.println("sssssssss");
            System.out.println(referenceQueue.poll());
        }
    }
    
    im a say()
    A类的对象被垃圾回收器清理
    

    虚引用在任何时候,都会被垃圾回收

    启用System.gc()开启了一个子线程进行垃圾回收;加入System.out.println("sssssssss");等同于有了一个时间差,给System.gc()有了清理对象的时间,清理后就会将该对象放入引用队列中去;

    因为在使用PhantomReference重写了get()方法,让其返回为null,所以phantomReference.get()也找不回来被虚引用引用的对象


引用队列,指的是对象被清理之后放入的地方,不仅可以和虚引用同时使用,也可以和其他引用同时使用。


3 垃圾回收算法

3.1 标记-清除算法

“标记-清除”算法是最基础的收集算法。

算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象(标记过程通过可达性分析实现)。后续的收集算法都是基于这种思路并对其不足加以改进而已。

“标记-清除”算法的不足主要有两个:

效率问题:标记和清除这两个过程的效率都不高

空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在 程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。

在这里插入图片描述

3.2 复制算法

“复制”算法是为了解决“标记-清除”的内存碎片问题。它将可用内存按容量划分为 大小相等的两块,每次只使用其中一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。

优点:整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等的复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。

缺点:由于每次都要预留出 50%的空间不能存放对象,内存利用率低,内存空间严重浪费。

在这里插入图片描述

3.3 标记-整理算法

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

整理过程依然麻烦,效率不高

在这里插入图片描述

4 JVM 分代垃圾回收机制

  1. 新生代垃圾回收

新生代使用用复制算法来进行垃圾回收,由于新生代中 98%的对象都是"朝生夕死"的, 所以并不需要按照 1 : 1 的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的 Eden(生成区)空间和两块较小的 Survivor(幸存者)空间

当 Survivor 空间不够用时,JVM 使用内存担保的机制将 Survivor 空间存活的对象转移到老年代。

HotSport 默认 Eden 与 Survivor 的大小比例是 8 : 1,也就是说 Eden:Survivor From : Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的 90%, 而剩下的 10%用来存放回收后存活的对象。

流程详见:JVM(超级无敌认真好用,万字收藏篇!!!)第五章-堆

  1. 老年代垃圾回收

老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-整理"算法


  • 学习来自于西安加中实训

猜你喜欢

转载自blog.csdn.net/woschengxuyuan/article/details/129979709