前置知识:
强引用,软引用,弱引用,虚引用介绍:
强引用是最常见的引用类型。当一个对象具有强引用时,即使内存空间不足,垃圾回收器也不会进行回收。只有在强引用不存在时,垃圾回收器才会回收该对象。
软引用是一种比强引用更弱的引用类型。当内存空间不足时,垃圾回收器可能会回收具有软引用的对象,但并不是一定会回收。软引用可以用来实现缓存功能。
弱引用是一种比软引用更弱的引用类型。当对象只有弱引用时,只要有gc就回收该对象,即使内存空间不紧张。弱引用通常用于实现对象的事件监听和缓存功能。
虚引用是最弱的一种引用类型。当对象只有虚引用时,垃圾回收器会确保在回收该对象之前,先通知相关的引用对象。虚引用主要用于在对象被回收时进行一些清理工作。
提供一种比finalize更灵活的机制。
分析:
线程栈中的自定义的ThreadLocal变量指向了堆中的ThreadLocal实例对象,这是毫无疑问的强引用,且如果只存在这一对引用关系,则线程销毁,ThreadLocal变量被GC回收的同时,堆中的实例对象也会被销毁,不会存在内存泄漏的问题。
但是,由于静态内部类ThreadLocalMap所使用的kv键值对,是以ThreadLocal引用的形式作为key,因此如果没有手动remove,那自然ThreadLocalMap就中会保留了一份指向堆中实例对象的引用。按照GC的可达性分析,因为Map中还存在一份指向堆中实例对象的引用,所以即使线程销毁,堆中的实例对象就不会被销毁,这个对象将一直驻留在内存中,造成内存泄漏。
而如果Map中的引用是弱引用,则只要垃圾回收开始,即使内存还存在一个弱引用指向堆中的实例对象,这个实例对象依然会被销毁,这样可以解决大多数的内存泄漏问题,但并不完美。
因为此时Map中的key就会指向null,这会导致新的问题。
分析:如果此时线程直接结束,万事大吉,因为value值的引用只存在当前线程的工作内存中,一旦线程销毁,关于堆中value对象的强引用也会随之销毁,进而销毁堆中的value实例对象。
但是,如果在线程池环境下,当前线程并不会被销毁,而是继续下一个任务。这将导致
Map中始终存在一个key=null,value=xxx的键值对。而value实例对象和对应的强引用就会一直驻留在内存中,造成内存泄漏。
解决方案:ThreadLocal的set,get,remove方法会自动扫描Map,将key=null的键值对的value设置为null,从而清除value到堆中实例对象的强引用链,交给
垃圾回收线程自动回收。
总结:在线程池环境下使用ThreadLoacl,一定要记得最后调用remove方法,以避免业务混乱和内存泄漏。