强引用、软引用、弱引用、虚引用以及垃圾回收机制

一、回收对象判定算法

1、引用计数器算法

在堆中的每一个对象都有一个引用计数器,在任意位置引用了该对象,则该对象的引用计数器加一。这种垃圾回收机制比较传统,其缺点是无法判断两个对象循环依赖的情况,即A调用B,且B又调用了A。因此大多数主流的JVM都不会使用这种方法。

2、可达性分析算法

可达性分析算法是将所有的对象转化为一个链式结构,若A调用了B,则A指向B。该链式结构的根节点为GCRoots,即从GCRoots作为起点向下索引。若发现一个对现象不在以GCRoots为根节点的调用链中,则该对象被JVM判定为不可达,被JVM标记为可回收对象,如下图:
强引用、软引用、弱引用、虚引用以及垃圾回收机制_第1张图片
图中,Object1-Object4均在以GCRoots为根节点的调用链中,则为不可回收对象;Object5-Object7虽然也存在调用,但不在GCRoots的调用链中,因此为可回收对象。

可作为GCRoots的对象一般都有一个特征,它只会引用其他变量,而不会被其他变量引用。一般可作为GCRoots的对象包含下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区(元空间)中类静态变量
  • 本地方法栈中的变量
  • 正在运行的线程

3、回收与自救

若一个对象重写了finalize()方法,则在JVM回收该对象时会调用该对象重写的finalize()方法,但只会执行一次。被回收的对象可在finalize()方法中实现一次自救。因此被标记为可回收的对象也不是非死不可,最多会经历两次被判定的过程。

JVM在可达性检测中发现了不可达的对象,首先会检查该对象是否重写了finalize()方法,若重写了该方法再判断是否调用过该方法,若没有重写finalize()方法或已被调用过finalize()方法则会被回收;若重写了finalize()方法且没有调用过的对象被判定为有必要执行finalize()方法,则会被JVM将其放置在一个名为F-Queue的队列中,JVM会自动生成一个优先级低的Finalizer的线程去执行F-Queue队列中对象的finalize()方法。若该对象的finalize()方法中引用了任意GCRoots调用链中的任意一个对象,则该对象会被判定为可达,不会被JVM回收,从而完成了一次自救。但自救只有一次,一个对象的finalize()方法只会被执行一次,下一次被判定为不可达时会直接被回收,第二次回收也被成为二次回收。

垃圾回收的判定逻辑如下:
强引用、软引用、弱引用、虚引用以及垃圾回收机制_第2张图片

4、回收方法区

JVM的方法区主要存放的是编译class文件后生成的Class类以及一些静态资源与常量等资源。常量的回收机制与对象的回收机制类似,即没有在任何一个地方引用该常量,则该常量会从常量池中销毁。类的回收机制相对复杂一些,需要同时满足以下三个条件:

  • 该类的所有实例都已经被回收了(java堆中不存在该类的实例)
  • 加载该类的ClassLoader已经被回收
  • 该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法
  • JVM可以对满足上述3个条件的无用类进行回收,也仅仅是"可以"而不是必然。在大量使用反射、动态代理等场景都需要JVM具备类卸载的功能来放置永久代的溢出。

二、强引用、软引用、弱引用、虚引用

1、前言

在jdk1.2之前,一个对象只有两种状态,要么可回收,要么不可回收,这中非黑即白的机制不利于程序的灵活性,因此在jdk1.2之后,jdk引入了四种对象引用状态级别,从高到低分别为:强引用>软引用>弱引用>虚引用

2、强引用

强引用就是我们普遍使用一个对象时的方法,即类似Object obj=new Object()这种的引用方法。在强引用时,JVM即使内存溢出,抛出OutOfMemoryError错误,使程序运行中断,也不会去回收强引用对象解决内存溢出问题。因此在强引用对象用完时需要将强引用弱化,可将对象置为空obj=null

3、软引用

软引用是指使用 SoftReference sf=new SoftRerence<>(obj)或继承SoftReferenct

public class Test extends SoftReference<User> {
    public Test(User referent) {
        super(referent);
    }
}

继承SoftReference对象时必须要实现有参构造器,且不能存在无参构造

如果一个对象只有软引用,内存充足时是不会对其进行回收的,只有当内存溢出时才会对其进行回收。JVM会尽可能优先回收闲置时间长的软引用,尽量保留新创建的软引用。

软引用也可以与引用队列ReferenceQueue联合使用,这样这个对象被回收时会被加入这个队列中,可以通过poll()方法来判断你关心的对象是否在队列中,如果没在队列中则返回null

ReferenceQueue<User> rq=new ReferenceQueue<>();
User user=new User();
SoftReference sf<User>=new SoftReference(user,rq);
Reference<? extends User> poll=rq.poll();

软引用可以通过get()方法,获取到这个对象的强引用对象,可以直接使用

ReferenceQueue<User> rq=new ReferenceQueue<>();
User user=new User();
SoftReference sf<User>=new SoftReference(user,rq);
User user=sf.get();

软引用一般用户内存敏感的高速缓存

比如在页面缓存中,进入到下一个页面中可以回退到上一个页面。如果上一个页面以强引用缓存在内存中,会造成内存泄露,随着页面访问量的增加,容易造成内存溢出。而如果将上一个页面回收掉,在回退到上一个页面时再重新加载页面会造成资源的紧张。因此如果将上一个页面以软引用缓存再内存中可以解决如上问题,不仅不需要重新加载页面,随着页面访问量的增加,当内存满溢时会回收掉最早访问的页面保留最近访问的页面。

4、弱引用

弱引用是指使用 WeakReference wf=new WeakRerence<>(obj)或继承WeakReference

public class Test extends WeakReference<User> {
    public Test(User referent) {
        super(referent);
    }
}

继承WeakReference对象时必须要实现有参构造器,且不能存在无参构造

如果一个对象只有弱引用,无论内存是否充足,只要垃圾回收线程扫描到了弱引用,则会立即对其进行回收;因此相较于软引用来说,只拥有弱引用的对象具有更短暂的生命周期;但是垃圾回收线程的优先级很低,因此不一定会很快发现那些只具有弱引用的对象。

若引用也可以与引用队列ReferenceQueue联合使用,这样这个对象被回收时会被加入这个队列中,可以通过poll()方法来判断你关心的对象是否在队列中,如果没在队列中则返回null

ReferenceQueue<User> rq=new ReferenceQueue<>();
User user=new User();
WeakReference sf<User>=new WeakReference(user,rq);
Reference<? extends User> poll=rq.poll();

WeakReference并没有实现get方法,因此弱引用无法获得强引用对象。

弱引用也可用于内存敏感的高速缓存区

例如ThreadLocal中,有一个静态内部类ThreadLocalMap,该哈希表是以开放地址法解决哈希冲突的。其key为ThreadLocal对象,value为ThreadLcoal所对应的值。该类内还有一个静态内部类Entry,其依赖链为ThreadLocal->ThreakLocalMap->Entry->key,当ThreadLocal用完时,将其置为null,由于ThreadLocalMap为静态内部类,其中还存在ThreadLocalMap->Entry->key的引用,则ThreadLocal并不会被回收,会造成内存泄露。JDK将Entry设置为WeakReference,当取消了ThreadLocal的强引用时,就只剩下一个弱引用,当垃圾回收线程扫描时会将其进行回收
强引用、软引用、弱引用、虚引用以及垃圾回收机制_第3张图片

5、虚引用

虚引用是指使用PhantomReference pf=new PhantomReference<>(obj)或继承PhantomReference

public class Test extends PhantomReference<User> {

    /**
     * Creates a new phantom reference that refers to the given object and
     * is registered with the given queue.
     *
     * 

It is possible to create a phantom reference with a null * queue, but such a reference is completely useless: Its get * method will always return null and, since it does not have a queue, it * will never be enqueued. * * @param referent the object the new phantom reference will refer to * @param q the queue with which the reference is to be registered, */ public Test(User referent, ReferenceQueue<? super User> q) { super(referent, q); } }

继承PhantomReference对象时必须要实现有参构造器,且不能存在无参构造

虚引用必须要与引用队列ReferenceQueue联合使用,在垃圾回收线程准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。可以通过poll()方法来判断你关心的对象是否在队列中,如果没在队列中则返回null

虚引用也叫虚幻引用,顾名思义如同幽灵一样,引用没引用都一样,并不会决定对象的生命周期。若一个对象只有一个虚引用,那么这个对象就像没引用一样,在任何时候都有可能被JVM回收。且PhantomReferenct实现了get方法,但只返回null,因此也无法获取到强引用对象;且从定义上来说,虚引用不可访问
强引用、软引用、弱引用、虚引用以及垃圾回收机制_第4张图片

虚引用一般用于检测所引用的对象是否被回收

一个对象有一个强引用和一个虚引用,由于其有一个强引用,不会被回收。当强引用用完时将其置为null,此时这个对象只有一个虚引用,随时都有可能被回收;当这个对象被回收时,会被加入到引用队列中,通过查看引用队列可知道这个对象是否被回收。

你可能感兴趣的:(jvm,java,jvm)