前言
平时并发编程,除了保护批改共享变量的场景,有时咱们也须要为每一个线程设置一个公有的变量,进行线程隔离,java提供的ThreadLocal能够帮忙咱们实现,而讲到ThreadLocal则不得不讲讲java的四种援用,不同的援用类型在GC时体现是不一样的,援用类型Reference有助于咱们理解如何疾速回收某些对象的内存或对实例的GC管制
- 四种援用类型在JVM的生命周期
- 援用队列(ReferenceQueue)
- ThreadLocal的实现原理和应用
- FinalReference和finalize办法的实现原理
- Cheaner机制
关注公众号,一起交换,微信搜一搜: 潜行前行
1 四种援用类型在JVM的生命周期
强援用(StrongReference)
- 创立一个对象并赋给一个援用变量,强援用有援用变量指向时,永远也不会垃圾回收,JVM宁愿抛出OutOfMemory异样也不会回收该对象;强援用对象的创立,如
Integer index = new Integer(1);String name = "csc";
- 如果中断所有援用变量和强援用对象的分割(将援用变量赋值为null),JVM则会在适合的工夫就会回收该对象
软援用(SoftReference)
- 和强用援用不同点在于内存不足时,该类型援用对象会被垃圾处理器回收
- 应用软援用能避免内存泄露,加强程序的健壮性。SoftReference的特点是它的一个实例保留对一个Java对象的软援用,该软援用的存在不障碍垃圾收集线程对该Java对象的回收
- SoftReference类所提供的get()办法返回Java对象的强援用。另外,一旦垃圾线程回收该对象之后,get()办法将返回null
String name = "csc"; //软援用的创立 SoftReference<String> softRef = new SoftReference<String>(name); System.out.println(softRef.get());
弱援用(WeakReference)
- 特点:无论内存是否短缺,只有进行GC,都会被回收
String name = "csc"; //弱援用的创立 WeakReference<String> softRef = new WeakReference<String>(name); System.out.println(softRef.get()); //输入 csc System.gc(); System.out.println(softRef.get()); //输入 null //弱援用Map WeakHashMap<String, String> map = new WeakHashMap<String, String>();
虚援用(PhantomReference)
- 特点:如同虚设,和没有援用没什么区别;虚援用和软援用、弱援用不同,它并不决定对象的生命周期。如果一个对象与虚援用关联,则跟没有援用与之关联一样,在任何时候都可能被垃圾回收器回收
- 要留神的是,虚援用必须和援用队列关联应用,当垃圾回收器筹备回收一个对象时,如果发现它还有虚援用,就会把这个虚援用退出到与之关联的援用队列中。程序能够通过判断援用队列中是否曾经退出了虚援用,来理解被援用的对象是否将要被垃圾回收
public static void main(String[] args) { String name = "csc"; ReferenceQueue<String> queue = new ReferenceQueue<String>(); PhantomReference<String> pr = new PhantomReference<String>(name, queue); //PhantomRefrence的get办法总是返回null,因而无法访问对应的援用对象。 System.out.println(pr.get()); // null System.gc(); System.out.println(queue.poll()); //获取被垃圾回收的"xb"的援用ReferenceQueue}
援用类型 | 被垃圾回收工夫 | 场景 | 生存工夫 |
---|---|---|---|
强援用 | 从来不会 | 对象的个别状态 | JVM进行运行时终止 |
软援用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱援用 | 失常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚援用 | 失常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
2 援用队列(ReferenceQueue)
- 援用队列能够配合软援用、弱援用及虚援用应用;当援用的对象将要被JVM回收时,会将其退出到援用队列中
ReferenceQueue<String> queue = new ReferenceQueue<String>(); WeakReference<String> pr = new WeakReference<String>("wxj", queue); System.gc(); System.out.println(queue.poll().get()); // 获取行将被回收的字符串 wxj
3 ThreadLocal的原理和应用
ThreadLocal 的实现原理
- 每个线程都内置了一个ThreadLocalMap对象
public class Thread implements Runnable { /* 以后线程对于的ThreadLocalMap实例,ThreadLocal<T>作为Key, * T对应的对象作为value */ ThreadLocal.ThreadLocalMap threadLocals = null;
- ThreadLocalMap作为ThreadLocal的外部类,实现了相似HashMap的性能,它元素Entry继承于WeakReference,key值是ThreadLocal,value是援用变量。也就是说jvm产生GC时value对象则会被回收
public class ThreadLocal<T> { //ThreadLocal对象对应的hash值,应用一个动态AtomicInteger实现 private final int threadLocalHashCode = nextHashCode(); //设置value public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } //获取value public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { //获取以后线程的ThreadLocalMap,再应用对象ThreadLocal获取对应的value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } .... //相似HashMap的类 static class ThreadLocalMap { //应用凋谢地址法解决hash抵触 //如果hash出的index曾经有值,通过算法在前面的若干地位寻找空位 private Entry[] table; ... //Entry 是弱援用 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
ThreadLocal不保障共享变量在多线程的安全性
- 从ThreadLocal的实现原理可知,ThreadLocal只是为每个线程保留一个正本变量,正本变量的批改不影响其余线程的变量值,因而ThreadLocal不能实现共享变量的安全性
ThreadLocal 应用场景
- 线程平安,包裹线程不平安的工具类,比方java.text.SimpleDateFormat类,当然jdk1.8曾经给出了对应的线程平安的类java.time.format.DateTimeFormatter
- 线程隔离,比方数据库连贯治理、Session治理、mdc日志追踪等。
ThreadLocal内存泄露和WeakReference
- ThreadLocalMap.Entry是弱援用,弱援用对象是不论有没有被援用都会被垃圾回收
- 产生内存透露个别是在线程池的线程,生命周期长,threadLocals援用会始终存在,当其寄存的ThreadLocal被回收(弱援用生命周期短)后,它对应的Entity成了e.get()==null的实例。线程不死则Entity始终不会被回收,这就产生了内存透露
- 如果线程跨业务操作雷同的ThreadLocal,还会造成变量平安问题
- 通常在应用完ThreadLocal最好调用它的remove();在ThreadLocal的get、set的时候,最好查看以后Entity的key是否为null,如果是null就把Entity开释掉,value则会被垃圾回收
4 finalize办法的实现原理FinalReference
final class Finalizer extends FinalReference<Object> { private static ReferenceQueue<Object> queue = new ReferenceQueue<>(); /* Invoked by VM */ static void register(Object finalizee) { new Finalizer(finalizee); } private static class FinalizerThread extends Thread { .... public void run() { ... for (;;) { try { Finalizer f = (Finalizer)queue.remove(); //这里会实现Object.finalize的调用 f.runFinalizer(jla); .... } static { ... Thread finalizer = new FinalizerThread(tg); ... //执行Object.finalize的守护线程 finalizer.setDaemon(true); finalizer.start(); }
- cpu资源比拟稀缺的状况下FinalizerThread线程有可能因为优先级比拟低而提早执行finalizer对象的finalize办法
- 因为finalizer对象的finalize办法迟迟没有执行,有可能会导致大部分finalizer对象进入到old分代,此时容易引发old分代的gc,甚至fullgc,gc暂停工夫显著变长
5 Cheaner机制
- 上一篇文章有介绍到jdk1.8的Cleaner框架篇:ByteBuffer和netty.ByteBuf详解
欢送指注释中谬误
参考文章
- ThreadLocal原理及应用场景大揭秘
- JDK源码剖析之FinalReference齐全解读
- 一次 Young GC 的优化实际
- Netty资源泄露检测
- 防止应用Finalizer和Cleaner机制
- 【JAVA Reference】Cleaner 源码分析(三)