参考文档:

https://blog.csdn.net/u012834750/article/details/71646700

threadlocal内存泄漏:http://www.importnew.com/22039.html

什么是ThreadLocal

首先明确一个概念,那就是ThreadLocal并不是用来并发控制访问一个共同对象,而是为了给每个线程分配一个只属于该线程的变量,顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突,实现线程间的数据隔离。从线程的角度看,就好像每一个线程都完全拥有该变量

应用场景:

数据库连接池,session

一个简单栗子

public class ThreadLocalTest implements Runnable {
static ThreadLocal<String> tl = new ThreadLocal<String>(); public static void main(String[] args) {
for(int i=;i<;i++){
Thread t = new Thread(new ThreadLocalTest(), "t"+i);
t.start();
}
} @Override
public void run() {
tl.set(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+" == tname :"+tl.get());
}
}

输出:

t0 == tname :t0
t1 == tname :t1
t2 == tname :t2
t3 == tname :t3
t4 == tname :t4

源码分析

ThreadLocal持有一个静态类ThreadLocalMap 。ThreadLocalMap 维护了一个entry数组

 static class ThreadLocalMap {
 private Entry[] table;
}

Entry

        static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}

entry相当于一个键值对,key是ThreadLocal,value就是当前线程变量的值。其中ThreadLocal是弱引用
也就是说ThreadLocal维护了一个集合。这个集合包含所有线程与之对应的值
set()

   public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

其中Thread类持有属性ThreadLocal的静态类ThreadLocalMap

public class Thread implements Runnable{
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
 /**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

    创建localmap其实是初始化thread的localmap属性

 /**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
 private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not. 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);//删除map里key为null的entry
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

get()

 public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
  private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

InheritableThreadLocal(可继承父线程的localmap)

InheritableThreadLocal定义如下。重写了getMap,createMap两个方法。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
} /**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
} /**
* Create the map associated with a ThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the table.
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}

之所以重写这两个方法。是因为InheritableThreadLocal唯一的不同在于操作thread的local属性不一样。

public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null; /*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}

Thread初始化时,将父类localmap传递给子类

 private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) { if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}

ThreadLocal为什么会内存泄漏

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
但是这些被动的预防措施并不能保证不会内存泄漏:
    使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏(参考ThreadLocal 内存泄露的实例分析)
    分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。
为什么使用弱引用
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
我们先来看看官方文档的说法:
    To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
    为了应对非常大和长时间的用途,哈希表使用弱引用的 key
下面我们分两种情况讨论:
    key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏
    key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

不想看了 留一个问题 :什么时候算 持有entry key的强引用 ?

最新文章

  1. 由12306动态验证码想到的ASP.NET实现动态GIF验证码(附源码)
  2. mysql 获取权限
  3. Java学习——开端
  4. BCP command usage in SQL Server
  5. 电商ERP如何接入智选物流平台?
  6. Jasper_crosstab_display a value of field in crosstab total row
  7. Web 前端开发环境
  8. Visual Stuido 2010/2012 扩展:Quick Launcher,快速打开指定文件
  9. 微信小程序使用场景延伸:扫码登录、扫码支付
  10. 【转】Vim使用笔记
  11. 使用CSS达到文字首尾对齐效果
  12. 并发编程心得--synchronized
  13. django 三种缓存模式的使用及注意点
  14. 如何定制 antd 的样式(theme)
  15. 51Nod1824 染色游戏 【Lucas定理】【FMT】【位运算】
  16. 【BZOJ2245】[SDOI2011]工作安排(费用流)
  17. 执行一条SQL语句,插入多条数据!
  18. ubuntu配置telnet服务
  19. 三大集合框架之Set
  20. Java进阶知识点3:更优雅地关闭资源 - try-with-resource及其异常抑制

热门文章

  1. 《 .NET并发编程实战》阅读指南 - 第1章
  2. 初识Markdown
  3. asp.net web 项目 针对aspx和ashx的 IHttpHandlerFactory 开发
  4. 【翻译】Dockerfile参考
  5. WPF绑定(Binding)(4)
  6. 前端常见的HTML+CSS面试题(附答案)
  7. 实验吧——忘记密码了(vim备份文件,临时文件(交换文件))
  8. vue父组件引用多个相同的子组件传值
  9. .htaccess文件配置理解
  10. loadrunner 基本操作