JAVA的四种引用类型:强软弱虚(转载)

分类依据
根据垃圾回收器的回收情况来分类
类型介绍以及用处
强引用:普通引用,即Object o = new Object这样的引用的统称,一旦启用垃圾回收器,会对强引用进行可达性分析,只有垃圾对象才会被回收
软引用(SoftReference):软引用内部包含一个引用,常用于缓存,淘汰机制有点类似LRU,通过SoftReference m声明的m也是一个强引用,但是m会指向SoftReference中包含的那个软引用,但是强引用指向的是软引用,m可以通过调用get()来访问内部的软引用,只要内存不够用的情况下,之前的软引用就会被当做垃圾回收,测试程序如下,事先设置JVM的最大内存为20M,-Xmx20M,输出为两个对象地址和一个null:
public class SoftRef {
public static void main(String[] args) {
SoftReference<byte[]> sr = new SoftReference<>(new byte[1024102410]);
System.out.println(sr.get());
System.gc();
try {
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(sr.get());
byte[] b = new byte[1024102415];
System.out.println(sr.get());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
弱引用(WeakReference):面试常问,弱引用比软引用更脆弱,只要垃圾回收器执行,弱引用就会被回收,在ThreadLocal中使用,防止内存泄露。测试程序如下:
public class WeakRef {
public static void main(String[] args) {
WeakReference<byte[]> w = new WeakReference<>(new byte[1024*1024]);
System.out.println(w.get());
System.gc();
System.out.println(w.get());
}
1
2
3
4
5
6
7
虚引用:
虚引用指向的对象是无法被访问的,这种引用的作用在于管理JVM的堆外内存(一般是在操作系统而不是在JVM中的对象,比如DirectorByteBuffer)。
创建的时候会有两个参数,一个是引用的对象T,另一个是ReferenceQueue队列,其中,队列用于记录对象是否被回收,换句话说,一旦虚引用指向的对象被回收,那么就往队列中插入回收消息。
用于判断堆外内存的情况,用于配合NIO中的DirectedByteBuffer(这个缓冲数组减少了一次从操作系统到JVM的拷贝开销),一旦回收队列中有了消息,说明对应引用所指向的空间已经是垃圾空间,需要被回收。
ThreadLocal
ThreadLocal是什么:线程局部变量,该变量所有线程都可见,每个线程都可以通过set()方法改变里面的属性,通过get()方法获取里面的属性。但是每个线程对它的操作都只是自身线程可见。
ThreadLocal应用场景:用于在同一个线程中需要保持一致,在不同线程中可能不同的变量,比如线程优先级, 线程对数据库的连接等。
ThreadLocal实现原理:关键在于set(T value)和get()方法
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);
}
}
1
2
3
4
5
6
7
8
9
在set方法中,关键就是一个map,这个map对象是来自于当前线程的,它将ThreadLocal的this指针作为key,并将传入的对象作为value。
因此隔离的原理并不在于ThreadLocal对象的独一无二,而是在于这个map是每个线程独有的,因此实现的时候,相当于把ThreadLocal这个东西装进了不同的瓶子里面。
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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
看完set之后,看get就是一个道理了,同样也是一个map,将this指针传入之后,得到的元素就是我们需要的ThreadLocal对象。

总结:其实ThreadLocal对外表现像是一个变量,但是在理解的时候,最好不要认为是一个变量,而是一种方法类,这个方法类利用currentThread结合map,实现了一个对象能够适配多个线程的情况。

ThreadLocal与弱引用
ThreadLocal为什么会引起内存泄露?

我们需要再深入了解一下set方法中的getMap,里面其实就一个return,threadLocals是Thread类中的一个Map类型变量。
//ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
1
2
3
4
5
6
ThreadLocal与弱引用

此时需要再深究一下ThreadLocalMap这个容器,这个容器放入的是一个个Entry对象,而Entry类则是继承自WeakReference<ThreadLocal<?>>类的,代码如下
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

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

1
2
3
4
5
6
7
8
9
从super(k)我们可以看到,这是调用了父类的构造函数来初始化ThreadLocal对象,这就意味着传进来的ThreadLocal对象会被弱引用指向。
为什么一定要弱引用呢?
如果不是弱引用的话,这里会导致内存泄漏(也就是本应该清理的时候没有清理)
内存泄漏:对于ThreadLocal对象而言,除了在方法中创建的tl引用指向它,还有线程中的ThreadLocalMap的key是指向它的,这会导致一个很严重的问题,就是即便对应方法已经结束,tl已经设置为null,此时逻辑上tl已经是垃圾对象,但是因为线程中,map有一个key仍指向它,这会导致tl仍然可达,不被认为是垃圾对象,一直要等到对应线程结束才会被清理。当线程是一个持久线程时,可能会有大量的无用局部变量未被回收。
不过光回收key仍然是不够的,因为当key==null的时候,map中就会存在一个<null, value>的Entry,这就让map中始终会有一个无法访问的value存在,因此在最后需要调用ThreadLocal.remove()方法来删除这个slot
参考
强软弱虚四种引用类型与ThreadLocal原理

原文链接:https://blog.csdn.net/Einskai216/article/details/106636124?utm_medium=distribute.pc_feed.419280.nonecase&depth_1-utm_source=distribute.pc_feed.419280.nonecase

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值