大数据、Java EE 学习资料请关注 B 站:https://space.bilibili.com/204792350

通过这篇文章,基本了解 Java 四种引用类型是什么咯

概念

Java 执行 GC 判断对象是否存活有两种方式:

  • 引用计数
    • Java 堆中每一个对象都有一个引用计数属性,引用每新增 1 次计数加 1,引用每释放 1 次计数减 1。
    • 对象可达性分析:通过一些列:“GC ROOTS” 的对象作为起点进行向下搜索,一旦对象没有在 “GC ROOTS” 中没有相连的引用链,那么该对象是可回收的;

在Java语言中,可作为GC Roots的对象包含以下几种:

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

在 JDK 1.2 以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于(reachable)可达状态,程序才能使用它。

从 JDK 1.2 版本开始,对象的引用被划分为 4 种级别,从而使程序能更加灵活地控制对象的生命周期。

这 4 种级别由高到低依次为:

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

Java 中提供这四种引用类型主要有两个目的:

  • 可以让程序员通过代码的方式决定某些对象的生命周期
  • 有利于JVM进行垃圾回收

强引用(StrongReference)

强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的 object 和 str 都是强引用:

public void test() {
    String str = "hello";
}

只要某个对象有强引用与之关联,GC 必定不会回收这个对象,即使在内存不足的情况下,Java 虚拟机宁愿抛出OutOfMemoryError 错误也不会回收这种对象。比如下面这段代码:

public static void main(String[] args) {
    fun1();
}

public static void fun1() {
    Object object = new Object();
    Object[] objArr = new Object[1000];
}

当运行至 Object[] objArr = new Object[1000] 这段代码时,如果内存不足,JVM 会抛出 OOM 错误也不会回收 object 指向的对象。

不过要注意的是,当 fun1 运行完之后,object 和 objArr 都已经不存在了,所以它们指向的对象都会被 JVM 回收。

如果强引用对象不使用时,需要弱化从而使GC能够回收,如下:

str = null;

显式地设置 str 对象为 null,或让其超出对象的生命周期范围,则 GC 认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于 GC 算法。

在一个方法的内部有一个强引用,这个引用保存在 Java 栈中,而真正的引用内容 (Object) 保存在 Java 堆中。
当这个方法运行完成后,就会退出方法栈,则引用对象的引用数为0,这个对象会被回收。

但是如果这个 str全局变量时,就需要在不用这个对象时赋值为 null,因为强引用不会被垃圾回收。

软引用(SoftReference)

软引用对象,由垃圾收集器根据内存需求自行清除。软引用最常用于实现对内存敏感的缓存。

如果一个对象只具有软引用,则内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

public void test() {
    String str = new String("SoftReference");
    SoftRenference<String> soft = new SoftRenference(str);
}

通过一个 SoftRenference 对象来创建一个软引用。

软引用还可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。

@Test
public void test1() {
    String string = "ReferenceQueue";
    ReferenceQueue<String> queue = new ReferenceQueue<>();
    SoftReference<String> reference = new SoftReference<>(string, queue);

    System.out.println(reference.get()); // ReferenceQueue
    System.out.println(queue.poll()); // null

    string = null;
    System.gc();

    System.out.println(reference.get()); // ReferenceQueue
    System.out.println(queue.poll()); // null
}

注意:

软引用对象是在 jvm 内存不够的时候才会被回收,我们调用 System.gc() 方法只是起通知作用,JVM 什么时候扫描回收对象是 JVM 自己的状态决定的。就算扫描到软引用对象也不一定会回收它,只有内存不够的时候才会回收。

当内存不足时,JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收。也就是说,GC 会在虚拟机抛出 OutOfMemoryError 之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用的软引用对象。对那些刚构建的或刚使用过的“较新的”软对象会被虚拟机尽可能保留,这就是引入引用队 ReferenceQueue 的原因。

应用场景:

  • 浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

  • 如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建;
    如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出。

伪代码例子:

public void test() {
    // 获取浏览器对象进行浏览
    Browser browser = new 浏览器对象
    // 从后台程序加载浏览页面
    BrowserPage page = browser.getPage();
    // 将浏览完毕的页面置为软引用
    SoftReference softReference = new SoftReference(page);

    // 回退或者再次浏览此页面时
    if(softReference.get() != null) {
        // 内存充足,还没有被回收器回收,直接获取缓存
        page = softReference.get();
    } else {
        // 内存不足,软引用的对象已经回收
        page = browser.getPage();
        // 重新构建软引用
        softReference = new SoftReference(page);
    }
}

弱引用(WeakReference)

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。

实例代码:

@Test
public void test2() {
    String string = new String("WeakRenference");
    WeakReference<String> weakReference = new WeakReference<>(string);
    System.out.println(weakReference.get()); // WeakRenference
    
    string = null;
    System.gc();
    
    System.out.println(weakReference.get()); // null
}

注意:

如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的 GC 垃圾回收,那么应该用 WeakReference 来记住此对象。

下面的代码会让一个弱引用再次为一个强引用

public void test() {
    String str = new String("abc");
    WeakReference<String> weakReference = new WeakReference<>(str);
    // 弱引用转强引用
    String strongReference = weakReference.get();
}

虚引用(PhantomReference)

虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

应用场景:

  • 虚引用主要用来跟踪对象被垃圾回收器回收的活动。

虚引用与软引用和弱引用的一个区别在于:

  • 虚引用必须和引用队列(ReferenceQueue)联合使用
  • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
public void test() {
    String str = new String("abc");
    ReferenceQueue queue = new ReferenceQueue();
    // 创建虚引用,要求必须与一个引用队列关联
    PhantomReference pr = new PhantomReference(str, queue);
}

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

进一步理解软引用和弱引用

对于强引用,我们平时在编写代码时经常会用到。而对于其他三种类型的引用,使用得最多的就是软引用和弱引用,这 2 种既有相似之处又有区别。它们都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在 JVM 进行垃圾回收时总会被回收。

在 SoftReference 类中,有三个方法,两个构造方法和一个 get 方法(WekReference 类似):

两个构造方法:

public SoftReference(T referent) {
    super(referent);
    this.timestamp = clock;
}
public SoftReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
    this.timestamp = clock;
}

get 方法用来获取与软引用关联的对象的引用,如果该对象被回收了,则返回 null。

在使用软引用和弱引用的时候,我们可以显示地通过 System.gc() 来通知 JVM 进行垃圾回收,但是要注意的是,虽然发出了通知,JVM 不一定会立刻执行,也就是说这句是无法确保此时JVM一定会进行垃圾回收的。

如何利用软引用和弱引用解决OOM问题

下面举个例子,假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。

设计思路是:

用一个 HashMap 来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM 会自动回收这些缓存图片对象所占用的空间,从而有效地避免了 OOM 的问题。在 Android 开发中对于大量图片下载会经常用到。

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

public void addBitmapToCache(String path) {
    // 强引用的Bitmap对象
    Bitmap bitmap = BitmapFactory.decodeFile(path);

    // 软引用的Bitmap对象
    SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);

    // 添加该对象到Map中使其缓存
    imageCache.put(path, softBitmap);
}

public Bitmap getBitmapByPath(String path) {
    
    // 从缓存中取软引用的Bitmap对象
    SoftReference<Bitmap> softBitmap = imageCache.get(path);

    // 判断是否存在软引用
    if (softBitmap == null) {
        return null;
    }
    
    // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
    Bitmap bitmap = softBitmap.get();
    return bitmap;
}

总结

Java 中 4 种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收。

通过表格来说明一下,如下:

引用类型被垃圾回收时间用途生存时间
强引用从来不会对象的一般状态JVM 停止运行时终止
软引用当内存不足时对象缓存内存不足时终止
弱引用正常垃圾回收时对象缓存垃圾回收后终止
虚引用正常垃圾回收时跟踪对象的垃圾回收垃圾回收后终止
# Java  

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×