java 引用类型 和 threadlocal线程变量

  1. 强引用 就是直接引用 Object o = new Object()
  2. 软引用 java.lang.ref.softReference 在堆内存空间够用的情况下不进行gc,堆内存不够用的情况下会被gc掉 适合缓存使用,没回收之前是可以get到的
  3. 弱引用(重要) java.lang.ref.weakReference 只要有gc过程,直接gc掉,没回收之前是可以get到的
  4. 虚引用 java.lang.ref.phantomReference 管理 direct byte buffer 直接内存,jvm中的对象保存着这指向操作系统的物理内存地址,就是jvm直接内存的回收手段,一个虚引用对象被回收,会放入一个回收队列中,jvm线程会监控该队列,清理直接内存,虚引用无论有没有gc,都get不到对象

T 就是被引用的对象ThreadLocal 和 弱引用注意ThreadLocal entry中的key是弱引用,value是强引用(正对value强引用,建议调用remove进行清理)
1.为什么ThreadLocalMap使用弱引用存储ThreadLocal?
假如使用强引用,当ThreadLocal不再使用需要回收时,发现某个线程中ThreadLocalMap存在该ThreadLocal的强引用,无法回收,造成内存泄漏。
因此,使用弱引用可以防止长期存在的线程(通常使用了线程池)导致ThreadLocal无法回收造成内存泄漏。

2.那通常说的ThreadLocal内存泄漏是如何引起的呢?
我们注意到Entry对象中,虽然Key(ThreadLocal)是通过弱引用引入的,但是value即变量值本身是通过强引用引入。
这就导致,假如不作任何处理,由于ThreadLocalMap和线程的生命周期是一致的,当线程资源长期不释放,即使ThreadLocal本身由于弱引用机制已经回收掉了,但value还是驻留在线程的ThreadLocalMap的Entry中。即存在key为null,但value却有值的无效Entry。导致内存泄漏。
但实际上,ThreadLocal内部已经为我们做了一定的防止内存泄漏的工作。
即如下方法:

/**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

上述方法的作用是擦除某个下标的Entry(置为null,可以回收),同时检测整个Entry[]表中对key为null的Entry一并擦除,重新调整索引。
该方法,在每次调用ThreadLocal的get、set、remove方法时都会执行,即ThreadLocal内部已经帮我们做了对key为null的Entry的清理工作。
但是该工作是有触发条件的,需要调用相应方法,假如我们使用完之后不做任何处理是不会触发的。

总结

1、在代码逻辑中使用完ThreadLocal,都要调用remove方法,及时清理。
目前我们使用多线程都是通过线程池管理的,对于核心线程数之内的线程都是长期驻留池内的。显式调用remove,一方面是防止内存泄漏,最为重要的是,不及时清除有可能导致严重的业务逻辑问题,产生线上故障(使用了上次未清除的值)。
2、最佳实践:在ThreadLocal使用前后都调用remove清理,同时对异常情况也要在finally中清理。
ThreadLocal一般加static修饰,同时要遵循第一条及时清理

你可能感兴趣的:(java 引用类型 和 threadlocal线程变量)