我们为什么要使用Java的弱引用?

1,008 阅读9分钟

哈喽,各位小伙伴们,你们好呀,我是喵手。

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

在Java开发中,内存管理一直是一个重要的话题。由于Java自动内存分配和垃圾回收机制的存在,我们不需要手动去管理内存,但是有时候我们却需要一些手动控制的方式来减少内存的使用。本文将介绍其中一种手动控制内存的方式:弱引用。

摘要

本文主要介绍了Java中弱引用的概念和使用方法。通过源代码解析和应用场景案例的分析,详细阐述了弱引用的优缺点以及适用的场景。最后,给出了类代码方法介绍和测试用例,并进行了全文小结和总结。

Java之弱引用

简介

弱引用是Java中一种较为特殊的引用类型,它与普通引用类型的最大不同在于,当一个对象只被弱引用所引用时,即使该对象仍然在内存中存在,也可能被垃圾回收器回收。

源代码解析

在Java中,弱引用的实现是通过WeakReference类来实现的。该类的定义如下:

public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent);
    public WeakReference(T referent, ReferenceQueue<? super T> q);
    public T get();
}

其中,构造方法分别是无参构造方法、有参构造方法和获取弱引用所引用的对象。

与强引用类型不同,弱引用不会对对象进行任何引用计数,也就是说,即使存在弱引用,对象的引用计数也不会增加。

  如下是部分源码截图:

在这里插入图片描述

应用场景案例

缓存

在开发中,缓存是一个很常见的场景。但是如果缓存中的对象一直存在,就会导致内存不断增加。这时,我们就可以考虑使用弱引用,在当缓存中的对象已经没有强引用时,该对象就会被回收。

Map<String, WeakReference<User>> cache = new HashMap<>();

public User getUser(String userId) {
    User user;
    // 判断是否在缓存中
    if (cache.containsKey(userId)) {
        WeakReference<User> reference = cache.get(userId);
        user = reference.get();
        if (user == null) {
            // 从数据库中读取
            user = db.getUserById(userId);
            // 加入缓存
            cache.put(userId, new WeakReference<>(user));
        }
    } else {
        // 从数据库中读取
        user = db.getUserById(userId);
        // 加入缓存
        cache.put(userId, new WeakReference<>(user));
    }
    return user;
}

上述代码中,我们在使用缓存时,首先判断该对象是否在缓存中。如果存在弱引用,我们先通过get()方法获取对象,如果对象不为null,则直接返回;如果对象为null,则说明该对象已经被回收了,此时需要从数据库中重新读取对象,并加入缓存。

监听器

在Java开发中,我们经常需要使用监听器。但是如果监听器存在强引用,当我们移除监听器时,由于其存在强引用,导致内存无法释放。使用弱引用则可以解决该问题。

public class Button {
    private List<WeakReference<ActionListener>> listeners = new ArrayList<>();

    public void addActionListener(ActionListener listener) {
        listeners.add(new WeakReference<>(listener));
    }

    public void removeActionListener(ActionListener listener) {
        listeners.removeIf(ref -> ref.get() == null || ref.get() == listener);
    }

    public void click() {
        for (WeakReference<ActionListener> ref : listeners) {
            ActionListener listener = ref.get();
            if (listener != null) {
                listener.perform();
            }
        }
    }
}

上述代码中,我们使用了一个List来保存所有的监听器。在添加监听器时,我们使用了WeakReference进行包装,以保证该监听器不会导致内存泄漏。在移除监听器时,通过removeIf()方法来匹配弱引用是否已经被回收,并且判断是否与指定的监听器相同。在触发事件时,我们通过get()方法获取弱引用所引用的对象,并判断是否为null,如果不为null,则执行监听器的perform()方法。

优缺点分析

优点

  1. 可以有效地降低内存占用;
  2. 适用于一些生命周期较短的对象,可以避免内存泄漏;
  3. 使用方便,只需要将对象包装为弱引用即可。

缺点

  1. 对象可能被提前回收,这可能会导致某些操作失败;
  2. 弱引用需要额外的开销,会对程序的性能产生一定的影响。

类代码方法介绍

WeakReference类

构造方法

public WeakReference(T referent);
public WeakReference(T referent, ReferenceQueue<? super T> q);

其中,第一个构造方法是无参构造方法,直接使用该方法会创建一个没有关联队列的弱引用。第二个构造方法需要传入一个ReferenceQueue队列,用于关联该弱引用。在目标对象被回收时,该队列会触发一个通知。

get()方法

public T get();

该方法用于获取弱引用所包装的对象,如果对象已经被回收,则返回null。

ReferenceQueue类

构造方法

public ReferenceQueue();

无参构造方法,直接使用该方法可以创建一个新的ReferenceQueue对象。

poll()方法

public Reference<? extends T> poll();

该方法用于获取ReferenceQueue队列中的下一个元素,如果队列为空,则返回null。

测试用例

测试代码演示

package com.example.javase.se.classes.weakReference;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author ms
 * @Date 2023-11-05 21:43
 */
public class WeakReferenceTest {

    public static void main(String[] args) throws InterruptedException {
        testWeakReference();
        testCache();
        testButton();
    }

    public static void testWeakReference() throws InterruptedException {
        User user = new User("123", "Tom");
        WeakReference<User> weakReference = new WeakReference<>(user);
        user = null;
        System.gc();
        Thread.sleep(1000);
        assert weakReference.get() == null;
    }

    public static void testCache() throws InterruptedException {
        User user = new User("123", "Tom");
        Map<String, WeakReference<User>> cache = new HashMap<>();
        cache.put(user.getId(), new WeakReference<>(user));
        user = null;
        System.gc();
        Thread.sleep(1000);
        assert cache.get("123").get() == null;
    }

    public static void testButton() {
        Button button = new Button();
        ActionListener listener1 = new ActionListener();
        ActionListener listener2 = new ActionListener();
        button.addActionListener(listener1);
        button.addActionListener(listener2);
        button.click();
        listener1 = null;
        listener2 = null;
        System.gc();
        assert button.getListeners().get(0).get() == null;
        assert button.getListeners().get(1).get() == null;
        button.click();
    }

    static class User {
        private String id;
        private String name;

        public User(String id, String name) {
            this.id = id;
            this.name = name;
        }

        public String getId() {
            return id;
        }

        public String getName() {
            return name;
        }
    }

    static class ActionListener {
        public void perform() {
            System.out.println("Button clicked");
        }
    }

    static class Button {
        private List<WeakReference<ActionListener>> listeners = new ArrayList<>();

        public void addActionListener(ActionListener listener) {
            listeners.add(new WeakReference<>(listener));
        }

        public void click() {
            for (WeakReference<ActionListener> ref : listeners) {
                ActionListener listener = ref.get();
                if (listener != null) {
                    listener.perform();
                }
            }
        }

        public List<WeakReference<ActionListener>> getListeners() {
            return listeners;
        }
    }
}

测试结果

  根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。

在这里插入图片描述

测试代码分析

  根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。

此代码演示了 Java 中弱引用的使用场景,以及如何使用弱引用来实现缓存和事件监听器等功能。主要包括以下内容:

1.测试弱引用:定义一个 User 类,通过 WeakReference 弱引用来持有此对象,并在程序运行时将 User 对象设为 null,通过 System.gc() 手动触发 GC,验证弱引用是否被回收。

2.测试缓存:定义一个 Map 对象,将 User 对象通过 WeakReference 弱引用的形式存入,保留 User 对象的 ID,在后续程序运行时手动触发 GC,验证弱引用是否被回收。

3.测试事件监听器:定义一个 Button 类,通过 List<WeakReference> 弱引用来持有 ActionListener 对象,定义一个 addActionListener 方法,用于向 List 中添加 ActionListener 对象,定义一个 click 方法,用于触发 ActionListener 中的 perform 方法。在测试中,向 Button 中添加两个 ActionListener 对象,将它们设为 null,通过 System.gc() 手动触发 GC,验证弱引用是否被回收。

总的来说,弱引用主要用于缓存、事件监听器等场景,可以避免内存泄漏问题,但需要注意使用时的一些问题,比如弱引用被回收后,需要手动进行相应的处理等。

全文小结

本文介绍了Java中弱引用的概念和使用方法,通过源代码解析和应用场景案例的分析,详细阐述了弱引用的优缺点以及适用的场景。同时,也给出了类代码方法介绍和测试用例,最后进行了全文小结和总结。

总结

本文介绍了Java中弱引用的概念和使用方法,弱引用是一种较为特殊的引用类型,与普通引用类型不同的是,当一个对象只被弱引用所引用时,即使该对象仍然在内存中存在,也可能被垃圾回收器回收。

弱引用主要适用于一些生命周期较短的对象,可以有效地降低内存占用。同时,在一些需要监听器、缓存等场景中,使用弱引用可以避免内存泄漏。

在使用弱引用时,我们可以使用WeakReference类来实现,并通过get()方法获取弱引用所包装的对象。同时,我们也可以使用ReferenceQueue类来关联弱引用,当目标对象被回收时,该队列会触发一个通知。

但是弱引用也有其缺点,例如对象可能被提前回收,这可能会导致某些操作失败,同时弱引用也需要额外的开销,会对程序的性能产生一定的影响。

因此,在使用弱引用时,我们需要根据具体场景具体分析,权衡其优缺点,选择合适的引用类型来进行内存管理。

... ...

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

... ...

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。