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

 更新时间:2021年10月08日 14:18:28   作者:Grey  
这篇文章介绍的是Java中的引用类型和使用场景,主要内容展开Java中的引用类型,有强引用、软引用 、弱引用、虚引用,需要的朋友可以参考一下

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;

/**
 * heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
 * 软引用,适合做缓存
 * 示例需要把Vm options设置为:-Xms20M -Xmx20M
 */
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;

/**
 * 弱引用遭到gc就会回收
 * ThreadLocal应用,缓存应用,WeakHashMap
 */
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<?>> {
            /** The value associated with this 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中的引用类型和使用场景内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决Mybatis查询方法selectById()主键不一致问题

    解决Mybatis查询方法selectById()主键不一致问题

    这篇文章主要介绍了解决Mybatis查询方法selectById()主键不一致问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • mybatis 字段名自动转小写的实现

    mybatis 字段名自动转小写的实现

    这篇文章主要介绍了mybatis 字段名自动转小写的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Maven访问仓库顺序代码实例解析

    Maven访问仓库顺序代码实例解析

    这篇文章主要介绍了Maven访问仓库顺序实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • java注解结合aspectj AOP进行日志打印的操作

    java注解结合aspectj AOP进行日志打印的操作

    这篇文章主要介绍了java注解结合aspectj AOP进行日志打印的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Java代理模式实例分析

    Java代理模式实例分析

    这篇文章主要介绍了Java代理模式,结合实例形式对比分析了java代理模式的使用方法与相关操作技巧,需要的朋友可以参考下
    2019-07-07
  • Java注解@Transactional事务类内调用不生效问题及解决办法

    Java注解@Transactional事务类内调用不生效问题及解决办法

    这篇文章主要介绍了Java注解@Transactional事务类内调用不生效问题及解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • Java基础之查找文本特定内容后进行修改

    Java基础之查找文本特定内容后进行修改

    这篇文章主要介绍了Java基础之查找文本特定内容后进行修改,文中有非常详细的代码示例,对正在学习java基础的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • Java实现五子棋游戏的完整代码

    Java实现五子棋游戏的完整代码

    这篇文章主要为大家详细介绍了Java实现五子棋游戏的完整代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-10-10
  • java导出Excel(非模板)可导出多个sheet方式

    java导出Excel(非模板)可导出多个sheet方式

    Java开发中,导出Excel是常见需求,有时需要支持多个Sheet导出,此技巧介绍非模板方式实现单标题单Sheet以及多Sheet导出,标题一致或不一致均可,可换成Map使用,适合个人开发者和需要Excel导出功能的场景
    2024-09-09
  • Java8接口中引入default关键字的本质原因详析

    Java8接口中引入default关键字的本质原因详析

    Default方法是在java8中引入的关键字,也可称为Virtual extension methods—虚拟扩展方法,这篇文章主要给大家介绍了关于Java8接口中引入default关键字的本质原因,需要的朋友可以参考下
    2022-01-01

最新评论