1、背景
ThreadLocal是线程内部的数据存储类,该存储类存储的对象只能在指定线程内部才能使用,其他线程无法获取
2、依赖的基础——常见的引用类型
从JDK 1.2
版本开始,对象的引用被划分为4
种级别,从而使程序能更加灵活地控制对象的生命周期。这4
种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
2.1 强引用(StrongReference)
强引用:某个对象没有指向它了,该对象就会被回收。否则,它永远不会被回收,直到OOM(OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”)
- 测试代码
public class StrongReference {
public static class Strong{
@Override
protected void finalize() throws Throwable {
//Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
System.out.println("finalize ....");
}
}
public static void main(String[] args) throws IOException {
Strong strong = new Strong();
// 此时没有指向strong对象,则会被垃圾回收机制回收掉
// strong = null;
System.out.println("s=" + strong);
System.gc();
System.in.read();// 阻塞main线程,给垃圾回收线程时间执行
}
}
- 结果
s=null
finalize ....
s=com.ysl.domain.StrongReference$Strong@2e817b38
2.2 软引用(SoftReference)
软引用:空间够的时候任意分配,空间不够的时候软引用会被回收。
为了测试方便设置JVM启动参数
-Xms20M -Xmx20M
- 测试代码
public class SoftRef {
public static void main(String[] args) throws InterruptedException {
SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);
System.out.println(softReference.get());
Thread.sleep(500);
System.out.println(softReference.get());
// 若heap空间不够,会先引用软引用回收掉
// 假如是强引用的话,会OOM
byte[] b = new byte[1024 * 1024 * 10];
System.out.println(softReference.get());
}
}
- 结果
[B@2e817b38
[B@2e817b38
null
2.3 弱引用(WeakReference)
弱引用:每次进行GC时会回收弱引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
/**
* @date: 2022/5/24
* @FileName: WeakRef
* @author: Yan
* @Des:
*/
public class WeakRef {
public static class MyObject{
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing myObject");
}
}
public static void main(String[] args) throws InterruptedException {
WeakReference<MyObject> wr = new WeakReference<>(new MyObject());
System.out.println(wr.get());
System.gc();
Thread.sleep(1000);
System.out.println(wr.get());
}
}
com.ysl.threadlocal.entity.WeakRef$MyObject@8efb846
finalizing myObject
null
2.4 虚引用(PhantomReference)
- 虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。
- 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
- 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
为了测试方便设置JVM启动参数
-Xms5M -Xmx5M
-
测试代码
/** * @date: 2022/5/24 * @FileName: PhantomRef * @author: Yan * @Des: */ public class PhantomRef { private static final List<byte[]> LIST = new ArrayList<>(); private static final ReferenceQueue<Person> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) { PhantomReference<Person> personPhantomReference = new PhantomReference<>(new Person(), QUEUE); System.out.println(personPhantomReference.get()); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); // 业务线程 new Thread(() -> { while (true){ LIST.add(new byte[1024 * 1024]); try { Thread.sleep(1000); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println("业务对象的personPhantomReference"+ personPhantomReference.get()); } }, "BussinessThread").start(); // gc线程 new Thread(() -> { while (true) { Reference<? extends Person> poll = QUEUE.poll(); if (poll != null) { System.out.println("--虚引用对象被jvm回收" + poll); } } }, "GCThread").start(); } }
-
结果
null --虚引用对象被jvm回收java.lang.ref.PhantomReference@2d46c162 业务对象的personPhantomReferencenull 业务对象的personPhantomReferencenull
3.ThreadLocal的应用场景
- 在同一个线程内参数的传递
- @Transactional
在同一个数据库中要进行install和update操作,就要获取数据库连接的connection,而当前线程是在同一个事务中,要保证事务的一致性,则conncetion必须是同一个才能保证事务,这个时候就可以用ThreadLocal,把Connection放在ThreadLocal中来进行存储和传递
4.内存泄露
内存泄露分两种
4.1 ThreadLocal自身的内存泄露问题
解决方案:ThreadLocal定义成弱引用(在某种程度上解决key不为null导致的内存泄露问题),这样就可以成功被
gc
回收
解释:
正常情况下我们的线程Thread
里面会定义一个map
,而这个map就是我们的ThreadLocalMap (通过阅读Thread的源码可以看到这一点)
而这个ThreadLocalMap
的key指向的是我们的ThreadLocal
对象,而这个ThreadLocal
中的set方法,在set的时候会指向一个this,this做为key,这个key指向的正是ThreadLocal
若把key的指向设置成强引用,然后把tl(ThreadLocal的一个对象实例)这个对象置为null,则此时,key是不会被gc回收的,这种情况下就会导致key的一个内存泄露问题,若设置成弱引用,每次gc的时候都可以被回收到;
而value值一般都是设置成强引用的,因为value存储的是我们的业务变量,一般情况下,业务变量是不允许其丢失的,所以是要设置为强引用,同样的这也会造成内存的泄露
4.2线程池下的内存泄露
这种内存泄漏并不是ThreadLocal tl =null导致,而是因为线程没有被回收,但是我们又不想使用该变量了,进而造成了对象一直存在于ThreadLocalMap中没有被回收,从而导致了内存泄漏。
解决方案:ThreadLocal使用完成后,及时调用remove方法(主要是解决value强引用导致的内存泄漏问题),将其从ThreadLocaMap中移除,从而避免内存泄漏。
注意∶
使用线程池时,线程用完后一定要对ThreadLocalMap (每个线程都有一个ThreadLocalMap) 进行清除操作(ThreadLocalMap map= null,彻底清除,回收整个map),否则后面再拿到该线程的人都可以读到之前的数据,时间长了,ThreadLocalMap也会被填满。
作者:我是你下药都得不到的男人
链接:https://juejin.cn/post/7101337995571101726
来源:稀土掘金
网友评论