四大引用是什么,分别有什么特点: 1 强引用、软引用、弱引用、虚引用 强引用:发生gc的时候,只要对象还有引用,就不会被回收 软引用:发生gc的时候,内存够用就不会回收,内存不够时,就会回收。可以及时的避免oom。 Map<String,SoftReference<BitMap>> imageCache = new HashMap<String,SoftReference<BitMap>>(); 弱引用:发生gc的时候,马上就会回收。 虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅仅持有虚引用,那么它和没有任何引用一样,在任何时候都可能被垃圾收集器回收。它不能单独使用也不通过通过它访问对象。虚引用必须和引用队列(ReferenceQueue)联合使用。 虚引用的目的是跟踪对象被垃圾回收的状态。仅仅是提供了确保对象被finalize之后做某些事情的机制。PhantomReference的get方法总是返回null. 四种引用垃圾回收的场景: 思考: 1 ThreadLocal中的ThreadLocalMap的数据结构与关系? Thread中有一个成员变量ThreadLocalMap。ThreadLocal中有一个静态内部类ThreadLocalMap,ThreadLocalMap中有一个静态内部类Entry. Entry是对ThreadLocal的弱引用。 2 Entry的key是弱引用,为什么? 每个Thread对象维护着ThreadLocalMap的引用,ThreadLocalMap是ThreadLocal的内部类,用Entry进行存储。 调用ThreadLocal的set方法时,实际是往ThreadLocalMap设置值,key是ThreadLocal对象, value是传进来的对象。 调用ThreadLocal的get方法时, 实际是从ThreadLocalMap中获取值。 ThreadLocal本身并不存储值,它只是以自己作为key从线程的ThreadLocalMap获取value, 正因为如此,ThreadLocal实现了线程隔离,获取当前线程的局部变量值,不受其他线程影响。 ThreadLocal提供线程局部变量,每一个线程在访问ThreadLocal实例的时候(通过其get与set方法)都有自己的,独立的初始化变量副本。Threadlocal实例通常是类中的私有静态字段,使用它的目的是希望将状态与线程关联起来。 //ThreadLocal的方法
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); // 使用this引用作为key,既做到了变量相关,又满足key不可变的要求。 else createMap(t, value); }//ThreadLocalMap的方法
private void set(ThreadLocal<?> key, Object value) { // map中就是使用Entry[]保留所有的entry实例 Entry[] tab = table; int len = tab.length; // 返回下一个哈希码 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //已经存在则替换旧值 if (k == key) { e.value = value; return; } //在设置期间清理哈希表为空的内容,保持哈希表的性质 if (k == null) { replaceStaleEntry(key, value, i); return; } } //不存在就新建一个entry tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }//ThreadLocal的方法
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(); }//ThreadLocalMap的方法
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } }想要存入的ThreadLocal中的数据实际上并没有存到ThreadLocal对象中去,而是以这个ThreadLocal实例作为key存到了当前线程中的一个Map中去了。 ThreadLocal内存泄露问题? 虚拟机栈栈中的ThreadLocal Ref 与堆上的ThreadLocal是强引用的关系,当虚拟机栈中的ThreadLocal Ref 用完之后,准备被回收时,如果entry与堆上的ThreadLocal不是弱引用的话,那么堆上的ThreadLocal对象就不会被回收。而如果是弱引用,当发生Gc时,ThreadLocal用完之后,不存在强引用,而唯一存在一个弱引用,此时ThreadLocal对象就可以被回收了。虽然弱引用可以让堆上的ThreadLocal对象被回收,但是Entry对象还因为线程的存在,有引用可达。ThreadLocal被回收之后,entry里面的key是null, 此时这个entry成为了陈旧项,value对象在线程存在时,仍然会存在,故而仍然有线程泄露的风险。因此就需要某种机制,来对Entry里面的key为null的value进行释放。ThreadLocal采用了线性探测来清除陈旧项(replaceStaleEntry与getEntryAfterMiss方法都干了这个活),从而防止了内存泄漏。当然,我们也可以在用完ThreadLocal之后,手动调用remove方法,去除线程中ThreadLocalMap中的这个entry. 4 ThreadLocal中为什么要加remove方法? 主要是为了及时地将线程中TreadLocalMap中的以这个ThreadLocal为key的entry中的value对象删除。这样就可以避免因为线程长期存在,而ThreadLocal实例用完之后导致的内存泄露问题。 总结: ThreadLocal并不解决线程间共享数据的问题,而是为每个线程提供了一个独立的变量副本。从而避免线程变量的线程安全问题。由于每个线程都有一个执属于自己的ThreadLocalMap,并维护了ThreadLocal与实例之间的映射关系,因此就不存在线程安全以及锁的问题。ThreadLocalMap的entry对ThreadLocal弱引用,避免了ThreadLocal无法被回收的问题。ThreadLocal自身的set、get、remove方法最终会调用expungeStaleEntry、cleanSomeSlots、replaceStaleEntry这三个方法回收键位null的entry对象的value值(实例),以及entry对象本身,从而防止内存泄露。 标签:ThreadLocalMap,探究,value,ThreadLocal,线程,引用,key,原理 From: https://www.cnblogs.com/mtjb1dd/p/17306651.html