文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java中的引用类型和使用场景详细

2024-04-02 19:55

关注

Java中的引用类型有哪几种?

Java中的引用类型分成 强引用 , 软引用 , 弱引用 , 虚引用 。

1、强引用

没有引用指向这个对象,垃圾回收会回收


package git.snippets.juc;

import java.io.IOException;

public class NormalRef {
    public static void main(String[] args) throws IOException {
        M m = new M();
        m = null;
        System.gc();
        System.in.read();
    }
    static class M {
        M() {}
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalized");
        }
    }
}

2、软引用

当有一个对象被一个软引用所指向的时候,只有系统内存不够用的时候,才会被回收,可以用做缓存(比如缓存大图片)

示例如下代码:注:执行以下方法的时候,需要把VM options设置为 -Xms20M -Xmx20M


package git.snippets.juc;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.concurrent.TimeUnit;


public class SoftRef {
    public static void main(String[] args) throws IOException {
        SoftReference<byte[]> reference = new SoftReference<>(new byte[1024 * 1024 * 10]);
        System.out.println(reference.get());
        System.gc();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(reference.get());
        byte[] bytes = new byte[1024 * 1024 * 10];
        System.out.println(reference.get());
        System.in.read();
    }
}

上述代码在第一次执行 System.out.println(reference.get()) 时候,由于堆的最大最小值都是 20M ,而我们分配的 byte 数组是 10M ,没有超过最大堆内存,所以执行垃圾回收,软引用不被回收,后续又调用了 byte[] bytes = new byte[1024 * 1024 * 10]; 再次分配了 10M 内存,此时堆内存已经超过设置的最大值,会进行回收,所以最后一步的 System.out.println(reference.get()); 无法 get 到数据。

3、弱引用

只要垃圾回收,就会回收。如果有一个强引用指向弱引用中的这个对象,如果这个强引用消失,这个对象就应该被回收。一般用在容器里面。

代码示例如下:


package git.snippets.juc;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;


public class WeakRef {
    public static void main(String[] args) {
        WeakReference<T> reference = new WeakReference<>(new T());
        System.out.println(reference.get());
        System.gc();
        System.out.println(reference.get());
    }
    static class T {
        T() {}
        @Override
        protected void finalize() {
            System.out.println("finalized");
        }
    }
}

如果执行了一次 GC reference.get() 获取到的值即为空。

4、弱引用的使用场景

弱引用的一个典型应用场景就是 ThreadLocal ,以下是 ThreadLocal 的的简要介绍

set方法:


public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


get方法:


public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }


ThreadLocalMap 是当前线程的一个成员变量,所以,其他线程无法读取当前线程设置的 ThreadLocal 值。


ThreadLocal.ThreadLocalMap threadLocals = null;


ThreadLocal 的主要应用场景

场景一:每个线程需要一个独享的对象:假设有100个线程都需要用到 SimpleDateFormat 类来处理日期格式,如果共用一个 SimpleDateFormat ,就会出现线程安全问题,导致数据出错,如果加锁,就会降低性能,此时使用 ThreadLocal ,给每个线程保存一份自己的本地 SimpleDateFormat ,就可以同时保证线程安全和性能需求。

场景二:每个线程内部保存全局变量,避免传参麻烦:假设一个线程的作用是拿到前端用户信息,逐层执行 Service1 Service2 Service3 Service4 层的业务逻辑,其中每个业务层都会用到用户信息,此时一个解决办法就是将 User 信息对象作为参数层层传递,但是这样会导致代码冗余且不利于维护。此时可以将 User 信息对象放入当前线程的 Threadlocal 中,就变成了全局变量,在每一层业务层中,需要使用的时候直接从 Threadlocal 中获取即可。

场景三: Spring 的声明式事务,数据库连接写在配置文件,多个方法可以支持一个完整的事务,保证多个方法是用的同一个数据库连接(其实就是放在 ThreadLocal 里面)

了解了 ThreadLocal 简要介绍以后,我们可以深入理解一下 ThreadLocal 的一个内部原理,前面提到, ThreadLocal 的 set 方法实际上是往当前线程的一个 threadLocals 表中插入一条记录,而这个表中的记录都存在一个 Entry 对象中,这个对象有一个key和一个value, key 就是当前线程的 ThreadLocal 对象。


static class Entry extends WeakReference<ThreadLocal<?>> {
            
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

这个 Entry 对象继承了 WeakReference , 且构造函数调用了 super(k) , 所以 Entry 中的 key 是通过一个弱引用指向的 ThreadLocal ,所以,我们在主方法中调用


ThreadLocal<Object> tl = new ThreadLocal<>();


tl 是通过强引用指向这个 ThreadLocal 对象。

当前线程的 threadLocalMap 中的 key 是通过弱引用指向 ThreadLocal 对象,这样就可以保证,在 tl 指向空以后,这个 ThreadLocal 会被回收,否则,如果 threadLocalMap 中的 key 是强引用指向 ThreadLocal 对象话,这个 ThreadLocal 对象永远不会被回收。就会导致内存泄漏。

但是,即便 key 用弱引用指向 ThreadLocal 对象, key 值被回收后, Entry 中的 value 值就无法被访问到了,且 value 是通过强引用关联,所以,也会导致内存泄漏,所以,每次在 ThreadLocal 中的对象不用了,记得要调用 remove 方法,把对应的 value 也给清掉。

5、虚引用

用于管理堆外内存回收

虚引用关联了一个对象,以及一个队列,只要垃圾回收,虚引用就被回收,一旦虚引用被回收,虚引用会被装到这个队列,并会收到一个通知(如果有值入队列,会得到一个通知)所以,如果想知道虚引用何时被回收,就只需要不断监控这个队列是否有元素加入进来了。

虚引用里面关联的对象用get方法是无法获取的。


import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;

// 配置 -Xms20M -Xmx20M
public class PhantomRef {
    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<P> QUEUE = new ReferenceQueue<>();


    public static void main(String[] args) {
        PhantomReference<P> phantomReference = new PhantomReference<>(new P(), QUEUE);
        new Thread(() -> {
            while (true) {
                LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                System.out.println(phantomReference.get());
            }
        }).start();

        new Thread(() -> {
            while (true) {
                Reference<? extends P> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
                }
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    static class P {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalized");
        }
    }
}

6、虚引用的应用场景

JDK的 NIO 包中有一个 DirectByteBuffer , 这个 buffer 指向的是堆外内存,所以当这个 buffer 设置为空的时候,Java的垃圾回收无法回收,所以,可以用虚引用来管理这个 buffer ,当我们检测到这个虚引用被垃圾回收器回收的时候,可以做出相应的处理,去回收堆外内存。

到此这篇关于Java中的引用类型和使用场景详细的文章就介绍到这了,更多相关Java中的引用类型和使用场景内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯