Java中的引用类型以及ThreadLocal

1、Java中的引用类型

1.1 强引用

Normal Reference【正常的引用】

当内存中没有东西指向它,就会被回收。

  • 重写T中的finalize()方法【对象实例被回收时会调用finalize()方法,为了能显式看到gc】
public class T {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}
  • 强引用对象,当没有引用指向它时,才会被回收
//强引用
public class NormalReference {
    public static void main(String[] args) throws IOException {
        T t=new T();    //建立一个强引用
        t=null;     //去除强引用
        System.gc();    //显式调用垃圾回收

        System.in.read();
    }
}

在这里插入图片描述

为什么最后要写System.in.read()呢?

因为如果不写这句代码的话,main方法退出了,垃圾回收是在单独的垃圾回收线程中,而不是在main线程中。有可能垃圾回收线程还没有进行垃圾回收,main线程结束,那就不能显式的看到垃圾回收的输出结果了。

1.2 软引用

Soft Reference【软引用本身首先是一个对象,再指向它所引用的对象。软引用对象实例本身都存放在堆内存中,它所引用的目标对象也是存放在堆内存中。】

内存示意图大致如下

SoftReference<byte []> m=new SoftReference<>(new byte[1024*1024*10]);

在这里插入图片描述

当堆内存空间不足时,先会进行一次垃圾回收,堆内存还不够,就会回收被软引用指向对象

  • 举例子
  1. 先通过-Xmx设置JVM运行时可申请的最大堆内存为20MB
    在这里插入图片描述

  2. 先建立软引用指向10MB的字节数组,再建立强引用指向10MB的字节数组。JVM会先垃圾回收一次,堆内存还是不够,就会回收软引用对象。因此第三次软引用m.get()输出为null

//软引用
public class T02_SoftReference {
    //设置 jvm堆内存参数为20MB
    public static void main(String[] args) throws InterruptedException {
        //虚引用指向对象 占10MB
        SoftReference<byte []> m=new SoftReference<>(new byte[1024*1024*10]);
        System.out.println(m.get());
        //堆内存存在空间不会被回收
        System.gc();
        Thread.sleep(500);
        //堆内存存在空间不会被回收,此时软引用仍存在
        System.out.println(m.get());

        //再在堆中分配一个数组,堆中装不下,先发生一次垃圾回收
        // 堆内存还不够,就会回收软引用对象
        byte[] b=new byte[1024*1024*10];
        System.out.println(m.get());
    }
}

在这里插入图片描述

1.3 弱引用

WeakReference【弱引用和软引用类似,本身都是一个对象,并且指向它们所引用的对象】

WeakReference<byte []> m=new WeakReference<>(new byte[1024*1024*10]);

内存示意图大致如下
在这里插入图片描述

只要发生垃圾回收,就会回收被弱引用指向的对象

使用场景:解决在Map的Entry内存管理中,可能会出现的内存泄漏问题。

  • 举例子
//弱引用
public class T03_WeakReference {
    public static void main(String[] args) {
        WeakReference<T> wr=new WeakReference<>(new T());
        System.out.println(wr.get());
        System.gc();
        System.out.println(wr.get());
    }
}

在这里插入图片描述

1.4 虚引用

虚引用指向对象被回收的时候,会把信息填到Queue中去。【当某个对象被回收时,我能知道它被回收了。】我们使用get()方法是获取不到的。

  • 举例子
package Reference;

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

public class T04_PhantomReference {
    private static final List<Object> list=new LinkedList<>();
    private static final ReferenceQueue<T> QUEUE=new ReferenceQueue<>();
    public static void main(String[] args) {
        PhantomReference<T> phantomReference=new PhantomReference<>(new T(),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());	//一直是null
           }
        }).start();
        new Thread(()->{
            while (true){
                Reference<? extends T> poll = QUEUE.poll();
                if (poll!=null){
                    System.out.println("--虚引用对象被JVM回收了--");
                }
            }
        }).start();
    }
}

  • 回收的消息被填充到QUEUE中
    在这里插入图片描述

使用场景:(使用get都get不到,用它干嘛?)管理堆外内存

JVM只占了操作系统中的一部分内存,当我们从网络中读数据时,操作系统首先读到操作系统内存中,然后再拷贝到JVM内存中,这样效率特别低,因此JVM提供了一种直接内存的方法,直接去访问操作系统的内存,但是不是JVM自己管理的内存,JVM是不知道也没有办法进行垃圾回收的。

因此,我们建立一个DirectByteBuffer虚引用,指向堆外内存。当这个虚引用对象被回收之后,可以通过QUEUE检测到,然后JVM清理对外内存。
在这里插入图片描述

2、ThreadLocal

2.1 概念

ThreadLocal可以为每一个线程提供自己专属的本地变量。

2.3 用法

  1. 创建ThreadLocal对象,并绑定需要提供的本地变量
    static ThreadLocal<Person> tl=new ThreadLocal<Person>();
  1. 各个变量通过ThreadLocal的set、get方法来设置和获取本地变量

2.3 内存泄漏相关概念

  • 内存溢出: Memory overflow 没有足够的内存提供申请者使用。
  • 内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果. 内存泄漏的堆积终将导致内存溢出。

2.4 原理【源码解读】

  • Thread类

    Thread类中有一个ThreadLocalMap对象 threadLocals
    在这里插入图片描述

  • ThreadLocal类

    ThreadLocal类中有一个静态类ThreadLocalMap,ThreadLocalMap中的Entry继承了弱引用,Entry中的key为虚引用指向当前ThreadLocal变量,value为我们set的值。
    在这里插入图片描述

  • 举例子

public class T05_ThreadLocal {
    static ThreadLocal<Person> tl=new ThreadLocal<Person>();
    public static void main(String[] args) {
        //Thread1
        new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(tl.get());
        }).start();
        //Thread2
        new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            tl.set(new Person(12));
        });
    }
}
  • Thread2会设置自己的ThreadLocalMap,因此Thread1调用get()方法为空
    在这里插入图片描述

注意事项:当tl=null后,意为解除了ThreadLocal的强引用,若key指向当前ThreadLocal为强引用,这个ThreadLocal就不会被回收,会发生内存泄漏。如果这里是弱引用,只要遇到垃圾回收,key所指向的ThreadLocal就会被回收。

注意事项2:key被回收后,此时entry变为(null,value),因此还要使用tl.remove()方法,回收ThreadLocalMap中的Entry。

  • 反正都要使用remove()回收entry,强引用和弱引用不是一样的吗?

弱引用多一层保护机制,当这个线程执行完后忘记remove,entry有的是会被回收掉的。比如ThreadLocalMap在调用set,get,remove方法时都会清理 key 为 null 的entry。强引用忘记remove,entry永远都不会回收掉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值