Java面试题(二十七) JVM圣地四大天骄 之 强引用、软引用、弱引用、虚引用

一. 强引用

1. 强引用 StrongReference

当内存不足,JVM开始垃圾回收,对于强引用对象,就算出现了OOM也不会堆该对象进行回收。

强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。

在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,
它处于可达状态,他是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM也不会回收。
因此强引用是造成Java内存泄漏的主要原因之一。

对于一个普通对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式的将相应(强)引用复制为null,
一般认为就是可以被垃圾收集的了

2. 代码演示:

public static void main(String[] args) {

        Object object1 = new Object(); // 这样 new 出来的,默认就是强引用
        Object object2 = object1; // 引用赋值

        object1 = null; // 置空
        System.gc(); // 垃圾回收

        System.out.println(object1); // null
        System.out.println(object2); // 没有影响 java.lang.Object@14ae5a5

    }

3. 运行结果:
在这里插入图片描述

二. 软引用

1. 软引用 SoftReference

软引用是一种相对强引用弱化了一些作用,需要用java.lang.ref.SOftReference类来实现,可以让对象豁免一些垃圾收集。

当系统内存充足时他不会被回收,当内存不足时会被回收。

软引用通常在对内存敏感的程序中,比如高速缓存就有用到软引用,内存足够的时候就保留,不够就回收。

2. 代码演示:

	// 内存足够时
    public static void softRef_Memory_Enough() {
        // 强引用
        Object o1 = new Object();
        // 软引用
        SoftReference<Object> softReference = new SoftReference<>(o1);
        // 输出
        System.out.println(o1); // java.lang.Object@14ae5a5
        System.out.println(softReference.get()); // java.lang.Object@14ae5a5

        // o1置空
        o1 = null;
        System.gc();
        // 输出
        System.out.println(o1); // null
        // 内存够用,软引用没有被 GC
        System.out.println(softReference.get()); // java.lang.Object@14ae5a5

    }

    // 内存不足时
    public static void softRef_Memory_NotEnough() {

        // 强引用
        Object o1 = new Object();
        // 软引用
        SoftReference<Object> softReference = new SoftReference<>(o1);
        // 输出
        System.out.println(o1); // java.lang.Object@14ae5a5
        System.out.println(softReference.get()); // java.lang.Object@14ae5a5

        // o1置空
        o1 = null;

        try {

            // 在 VM Options中配置内存为5M: -Xms5m -Xmx5m -XX:+PrintGCDetails
            // 故意配置大对象,让内存不够,发生OOM,看软引用的回收状况
            byte[] bytes = new byte[30 * 1024 * 1024]; // 模拟出30M的引用

        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            // 输出
            System.out.println(o1); // null
            System.out.println(softReference.get()); // null
        }
    }
public static void main(String[] args) {
		//内存足够时
        softRef_Memory_Enough();
        
		//内存不足时
        //softRef_Memory_NotEnough();

    }

3. 运行结果:
在这里插入图片描述
内存不足时:
在这里插入图片描述

三. 弱引用

1. 弱引用 WeakReference

弱引用需要用java.lang.ref.WeakReference类来实现,比软引用的生存期更短

只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收。

2. 代码演示:

public static void main(String[] args) {

        // 强引用
        Object o1 = new Object();
        // 弱引用
        WeakReference<Object> weakReference = new WeakReference<>(o1);
        // 输出
        System.out.println(o1); // java.lang.Object@14ae5a5
        System.out.println(weakReference.get()); // java.lang.Object@14ae5a5

        // 置空
        o1 = null;
        System.gc();

        // 输出
        System.out.println(o1); // null
        System.out.println(weakReference.get()); // null

    }

3. 运行结果:
在这里插入图片描述

四. 虚引用

1. 虚引用 PhantomReference

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

如果一个对象持有虚引用,那么他就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,他不能单独使用也不能通过它访问对象,
虚引用和引用队列(ReferenceQueeu)联合使用。

需应用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。

PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,
可以被gc回收,用来实现比finalization机制更灵活的回收操作

换句话说,设置虚引用关联的唯一目的,就是这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。

Java允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

2. 引用队列 ReferenceQueue

创建引用的时候可以指定关联的队列,当gc释放对象内存的时候,会把引用加入到引用队列,如果程序发现某个虚引用已经被加入到引用队列,
那么就可以在所引用的对象的内存被回收之前采取必要的行动,相当于通知机制

当关联的引用队列中有数据的时候,意味着引用指向的对内存中的对象被回收。

3. 代码演示:

public static void main(String[] args) throws InterruptedException {
        // 强引用
        Object o1 = new Object();
        // 引用队列
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        // 虚引用
        PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue);

        // 输出
        System.out.println(o1); // java.lang.Object@14ae5a5
        System.out.println(referenceQueue.poll()); // null
        System.out.println(phantomReference.get()); // null

        System.out.println("=======================");

        o1 = null;
        System.gc();

        // 输出
        System.out.println(o1); // null
        // 当GC后,关联引用队列的引用(如现在的虚引用),会被放到引用队列中
        // 引用队列,类似于AOP中的后置通知
        System.out.println(referenceQueue.poll()); // java.lang.ref.PhantomReference@7f31245a
        System.out.println(phantomReference.get()); // null

    }

4. 运行结果:
在这里插入图片描述

总结:四种引用最常见的常用的:就是前三种或者可以说是前两种。

如:一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取会严重影响性能,如果一次性全部加载到内存又可能造成内存溢出,这时可以用软引用解决这个问题

设计思路:用一个HashMap来保存图片路径和相应图片对象关联的软引用之间的映射关系,在内存不足时, JVM会自动共回收这些缓存图片对象所占的空间,避免OOM

 Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();

猜你喜欢

转载自blog.csdn.net/w_x_A__l__l/article/details/107120341