Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 79c0653

Browse files
[docs update]补充ThreadLocal内存泄漏相关说明
1 parent 0ba4ff0 commit 79c0653

File tree

1 file changed

+38
-12
lines changed

1 file changed

+38
-12
lines changed

‎docs/java/concurrent/java-concurrent-questions-03.md‎

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,47 @@ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
106106

107107
### ⭐️ThreadLocal 内存泄露问题是怎么导致的?
108108

109-
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。
109+
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。
110110

111-
这样一来,`ThreadLocalMap` 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。`ThreadLocalMap` 实现中已经考虑了这种情况,在调用 `set()``get()``remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后最好手动调用`remove()`方法
111+
如果 ThreadLocal 使用不当,就会发生内存泄漏,发生内存泄漏需要满足 2 个条件:
112112

113-
```java
114-
static class Entry extends WeakReference<ThreadLocal<?>> {
115-
/** The value associated with this ThreadLocal. */
116-
Object value;
113+
- ThreadLocal 被定义为方法的局部变量,从而导致 ThreadLocalMap 中的 key 在 GC 之后变为 null。
114+
- 线程持续存活(比如线程处在线程池中),导致线程内部的 ThreadLocalMap 对象一直未被回收。
117115

118-
Entry(ThreadLocal<?> k, Object v) {
119-
super(k);
120-
value = v;
121-
}
122-
}
123-
```
116+
**通过案例解释为什么会内存泄漏:**
117+
118+
假设将 ThreadLocal 定义为方法中的 **局部变量** ,那么当线程进入该方法的时候,就会将 ThreadLocal 的引用给加载到线程的 **** 中,假设为 ThreadLocalRef。
119+
120+
如下图所示,在线程栈 Stack 中,有两个变量,ThreadLocalRef 和 CurrentThreadRef,分别指向了声明的局部变量 ThreadLocal ,以及当前执行的线程内部的 ThreadLocalMap 变量。
121+
122+
![image-20241210225928979](https://11laile-note-img.oss-cn-beijing.aliyuncs.com/image-20241210225928979.png)
123+
124+
当线程执行完该方法之后,就会将该方法的局部变量从栈中删除。
125+
126+
因此 Stack 线程栈中的 ThreadLocalRef 变量就会被弹出栈,此时 ThreadLocal 变量的强引用消失了,现在只有 Entry 中的 key 对它进行弱引用。
127+
128+
那么这个 ThreadLocal 变量就会被垃圾回收器给回收掉,导致 Entry 中的 key 为 null,同时 value 指向了对 Object 的强引用。
129+
130+
同时假设当前这个线程一直存活,那么 Thread 内部的 ThreadLocalMap 变量就不会被回收,因此 ThreadLocalMap 内部的 Entry 的 value 指向的 Object 对象一直不会被回收,如下图(对线程的引用不一定在 Stack 栈中,还有可能在方法区,这里画在 Stack 栈中是为了方便理解):
131+
132+
![ThreadLocal 结构和内存泄漏](https://11laile-note-img.oss-cn-beijing.aliyuncs.com/ThreadLocal%20%E7%BB%93%E6%9E%84%E5%92%8C%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F.jpg)
133+
134+
**因此内存泄漏的发生需要满足 2 个条件:**
135+
136+
1、ThreadLocal 定义为方法内的局部变量,当方法执行完毕之后,ThreadLocal 被回收,导致无法通过 ThreadLocal 访问到 ThreadLocalMap 内部的 value。
137+
138+
2、Stack 线程栈内部的 CurrentThreadRef 引用指向的线程 **一直存活** ,导致线程内部的 ThreadLocalMap 也无法被回收,从而导致 Entry 的 value 一直存在指向 Object 的强引用,导致 Object 对象无法回收,出现内存泄漏。
139+
140+
JDK 团队也考虑到了这种情况,因此在设计 ThreadLocal 时还添加了清除 ThreadLocalMap 中 key 为 null 的 value 的功能,避免内存泄漏。这是在设计阶段为了避免内存泄漏而采取的措施,而我们使用的时候要保持良好的编程规范,正确定义 ThreadLocal,并且手动 remove,避免内存泄漏的发生。
141+
142+
结论:如果 `ThreadLocal` 被定义为方法的局部变量,并且线程一直存活,就会导致内存泄漏的发生。
143+
144+
**如何避免内存泄漏的发生?**
145+
146+
遵循阿里巴巴的开发规范:
147+
148+
- 将 ThreadLocal 变量定义成 `private static final` ,这样就一直存在 ThreadLocal 的强引用,也能保证任何时候都能通过 ThreadLocal 的访问到 Entry 的 value 值,进而清除掉。
149+
- 每次使用完 ThreadLocal 都主动调用它的 remove() 方法清除数据。
124150

125151
**弱引用介绍:**
126152

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /