关于java:基础篇JAVA引用类型和ThreadLocal

5次阅读

共计 4697 个字符,预计需要花费 12 分钟才能阅读完成。

前言

平时并发编程,除了保护批改共享变量的场景,有时咱们也须要为每一个线程设置一个公有的变量,进行线程隔离,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 源码分析(三)
正文完
 0