细数ThreadLocal三大坑,内存泄露仅是小儿科


细数ThreadLocal三大坑,内存泄露仅是小儿科
本文插图
苦味代码
看看 , 万一有用呢?
我在参加Code Review的时候不止一次听到有同学说:我写的这个上下文工具没问题 , 在线上跑了好久了 。 其实这种想法是有问题的 , ThreadLocal写错难 , 但是用错就很容易 , 本文将会详细总结ThreadLocal容易用错的三个坑: 内存泄露
线程池中线程上下文丢失
并行流中线程上下文丢失
由于ThreadLocal的key是弱引用 , 因此如果使用后不调用remove清理的话会导致对应的value内存泄露 。
@Testpublic void testThreadLocalMemoryLeaks { ThreadLocal<List<Integer>> localCache = new ThreadLocal<>; List<Integer> cacheInstance = new ArrayList<>(10000); localCache.set(cacheInstance); localCache = new ThreadLocal<>;} 当localCache的值被重置之后cacheInstance被ThreadLocalMap中的value引用 , 无法被GC , 但是其key对ThreadLocal实例的引用是一个弱引用 , 本来ThreadLocal的实例被localCache和ThreadLocalMap的key同时引用 , 但是当localCache的引用被重置之后 , 则ThreadLocal的实例只有ThreadLocalMap的key这样一个弱引用了 , 此时这个实例在GC的时候能够被清理 。
其实看过ThreadLocal源码的同学会知道 , ThreadLocal本身对于key为null的Entity有自清理的过程 , 但是这个过程是依赖于后续对ThreadLocal的继续使用 , 假如上面的这段代码是处于一个秒杀场景下 , 会有一个瞬间的流量峰值 , 这个流量峰值也会将集群的内存打到高位(或者运气不好的话直接将集群内存打满导致故障) , 后面由于峰值流量已过 , 对ThreadLocal的调用也下降 , 会使得ThreadLocal的自清理能力下降 , 造成内存泄露 。 ThreadLocal的自清理是锦上添花 , 千万不要指望他雪中送碳 。
相比于ThreadLocal中存储的value对象泄露 , ThreadLocal用在web容器中时更需要注意其引起的ClassLoader泄露 。
Tomcat官网对在web容器中使用ThreadLocal引起的内存泄露做了一个总结 , 详见:https://cwiki.apache.org/confluence/display/tomcat/MemoryLeakProtection , 这里我们列举其中的一个例子 。
熟悉Tomcat的同学知道 , Tomcat中的web应用由Webapp Classloader这个类加载器的 , 并且Webapp Classloader是破坏双亲委派机制实现的 , 即所有的web应用先由Webapp classloader加载 , 这样的好处就是可以让同一个容器中的web应用以及依赖隔离 。
下面我们看具体的内存泄露的例子:
public class MyCounter { private int count = 0; public void increment { count++; } public int getCount { return count; }}public class MyThreadLocal extends ThreadLocal<MyCounter> {}public class LeakingServlet extends HttpServlet { private static MyThreadLocal myThreadLocal = new MyThreadLocal; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MyCounter counter = myThreadLocal.get; if (counter == null) { counter = new MyCounter; myThreadLocal.set(counter); } response.getWriter.println( "The current thread served this servlet " + counter.getCount + " times"); counter.increment; }}