@@ -108,44 +108,54 @@ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
108
108
109
109
` ThreadLocal ` 内存泄漏的根本原因在于其内部实现机制。
110
110
111
- 源码版本:opebjdk8
112
- 内部主要围绕着threadlocalMap、threadlocal两个变量进行记录,同时需要理解的是该内存泄漏问题已经被解决,并不需要手动操作。
113
-
114
- threadlocalMap是线程级别变量,存储于线程变量Thread的成员变量引用。
115
- threadlocal是作为threadlocalMap的key,是我们手动为类添加的成员变量。
116
-
117
- 以threadlocal的set源码为例子:
118
-
119
- public void set(T value) {
120
- Thread t = Thread.currentThread();
121
- ThreadLocalMap map = getMap(t);
122
- if (map != null) {
123
- map.set(this, value);
124
- } else {
125
- createMap(t, value);
126
- }
127
- }
128
- 在map.set和createMap中,并没有真正存储key,而是通过threadlocal的hash值进行离散计算得到坐标,最终存储于类型为static class Entry extends WeakReference<ThreadLocal< ? > >的数组中。
129
- ` int i = key.threadLocalHashCode & (len-1); `
111
+ 通过上面的内容我们已经知道:每个线程维护一个名为 ` ThreadLocalMap ` 的 map。 当你使用 ` ThreadLocal ` 存储值时,实际上是将值存储在当前线程的 ` ThreadLocalMap ` 中,其中 ` ThreadLocal ` 实例本身作为 key,而你要存储的值作为 value。
130
112
131
- key不同于Map<K,V>接口的一般实现,使用node<k,V>将key和value一起存储,也就是说key只是作为钥匙,但并不存储于map当中,如果threadlocal变量的外部引用被抹除是可以正常被回收的。
113
+ ` ThreadLocal ` 的 ` set() ` 方法源码如下:
132
114
115
+ ``` java
116
+ public void set(T value) {
117
+ Thread t = Thread . currentThread(); // 获取当前线程
118
+ ThreadLocalMap map = getMap(t); // 获取当前线程的 ThreadLocalMap
119
+ if (map != null ) {
120
+ map. set(this , value); // 设置值
121
+ } else {
122
+ createMap(t, value); // 创建新的 ThreadLocalMap
123
+ }
124
+ }
125
+ ```
133
126
134
- 这也是Entry为何是弱引用的原因,因为可能会出现《null,v》的情况。这里虽然解决了内存泄漏的问题,但是似乎可能会导致Entry提前被回收?
127
+ ` ThreadLocalMap ` 的 ` set() ` 和 ` createMap() ` 方法中,并没有直接存储 ` ThreadLocal ` 对象本身,而是使用 ` ThreadLocal ` 的哈希值计算数组索引,最终存储于类型为 ` static class Entry extends WeakReference<ThreadLocal<?>> ` 的数组中。
135
128
136
- 查询得知可达性分析会有一条链路从threadlocal.get()方法所获得的变量,也就是间接可达,换句话说会考虑方法上的引用链条,而并非单纯是变量的直接引用。
129
+ ``` java
130
+ int i = key. threadLocalHashCode & (len- 1 );
131
+ ```
137
132
138
- 那么此时k存在v就一定存在,但是问题并没有结束,因为会出现k不存在,但是v存在的情况!
139
- 这是由于k实际上是类、方法的变量ThreadLocal,如果该变量被回收也就意味着内部的V永远不会被找到,成为了内存泄漏的原因。
133
+ ` ThreadLocalMap ` 的 ` Entry ` 定义如下:
140
134
135
+ ``` java
136
+ static class Entry extends WeakReference<ThreadLocal<?> > {
137
+ Object value;
141
138
142
- ** 重点在于key并非是弱引用,只是v作为map的直接引用无法被回收,因此使其成为了弱引用类型,并在set、get方法中做了一系列安全措施。**
139
+ Entry (ThreadLocal<?> k , Object v ) {
140
+ super (k);
141
+ value = v;
142
+ }
143
+ }
144
+ ```
143
145
146
+ ` ThreadLocalMap ` 的 ` key ` 和 ` value ` 引用机制:
144
147
148
+ - ** key 是弱引用** :` ThreadLocalMap ` 中的 key 是 ` ThreadLocal ` 的弱引用 (` WeakReference<ThreadLocal<?>> ` )。 这意味着,如果 ` ThreadLocal ` 实例不再被任何强引用指向,垃圾回收器会在下次 GC 时回收该实例,导致 ` ThreadLocalMap ` 中对应的 key 变为 ` null ` 。
149
+ - ** value 是强引用** :即使 ` key ` 被 GC 回收,` value ` 仍然被 ` ThreadLocalMap.Entry ` 强引用存在,无法被 GC 回收。
145
150
151
+ 当 ` ThreadLocal ` 实例失去强引用后,其对应的 value 仍然存在于 ` ThreadLocalMap ` 中,因为 ` Entry ` 对象强引用了它。如果线程持续存活(例如线程池中的线程),` ThreadLocalMap ` 也会一直存在,导致 key 为 ` null ` 的 entry 无法被垃圾回收,机会造成内存泄漏。
146
152
153
+ 也就是说,内存泄漏的发生需要同时满足两个条件:
147
154
155
+ 1 . ` ThreadLocal ` 实例不再被强引用;
156
+ 2 . 线程持续存活,导致 ` ThreadLocalMap ` 长期存在。
148
157
158
+ 虽然 ` ThreadLocalMap ` 在 ` get() ` , ` set() ` 和 ` remove() ` 操作时会尝试清理 key 为 null 的 entry,但这种清理机制是被动的,并不完全可靠。
149
159
150
160
** 如何避免内存泄漏的发生?**
151
161
0 commit comments