内存泄漏原因
- 强引用链:
ThreadLocal
实例通常以ThreadLocalMap
的键存在,ThreadLocalMap
是Thread
类的一个成员变量。当ThreadLocal
对象本身不再被外部强引用,但Thread
对象生命周期很长(比如线程池中的线程)时,由于ThreadLocalMap
中对ThreadLocal
的引用是WeakReference
,ThreadLocal
对象会被垃圾回收。然而,ThreadLocalMap
中的Entry
对象的value
(即ThreadLocal
绑定的值)仍然存在强引用,若该值较大且不释放,就可能导致内存泄漏。
- 过期Entry清理不及时:
ThreadLocalMap
在某些情况下(如扩容时)会清理过期的Entry
(ThreadLocal
为null
的Entry
),但如果在清理过期Entry之前,Thread
一直存活且不断向ThreadLocalMap
中添加新的Entry
,而旧的过期Entry
未被清理,value
就一直无法被回收,造成内存泄漏。
避免内存泄漏且优化性能的方法
- 手动调用remove方法:在使用完
ThreadLocal
后,及时调用remove()
方法。例如在try - finally
块中调用,确保无论业务逻辑是否正常结束,都能清理ThreadLocal
的值。
ThreadLocal<String> threadLocal = new ThreadLocal<>();
try {
threadLocal.set("value");
// 业务逻辑
} finally {
threadLocal.remove();
}
- 合理设置ThreadLocal的作用域:尽量缩短
ThreadLocal
的生命周期,只在必要的代码块内使用,减少其持有数据的时间,降低内存泄漏风险。
- 使用弱引用封装:如果
ThreadLocal
绑定的值本身就是大对象,可以考虑使用WeakReference
对其进行封装,进一步减少内存占用。
ThreadLocal<WeakReference<BigObject>> threadLocal = new ThreadLocal<>();
threadLocal.set(new WeakReference<>(new BigObject()));
WeakReference<BigObject> weakReference = threadLocal.get();
if (weakReference != null) {
BigObject bigObject = weakReference.get();
if (bigObject != null) {
// 使用bigObject
}
}
- 定期清理:如果使用线程池等长时间存活的线程,可在适当的时机(如线程空闲时)对
ThreadLocal
进行清理。可以通过自定义ThreadPoolExecutor
的afterExecute
方法来实现。
public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 清理ThreadLocal
// 例如:threadLocal.remove();
}
}