# ThreadLocal 内存溢出代码演示
下面我们用代码实现 ThreadLocal
内存溢出的情况,请参考以下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class ThreadLocalTest { static ThreadLocal threadLocal = new ThreadLocal(); static Integer MOCK_MAX = 10000; static Integer THREAD_MAX = 100; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(THREAD_MAX); for (int i = 0; i < THREAD_MAX; i++) { executorService.execute(() -> { threadLocal.set(new ThreadLocalTest().getList()); System.out.println(Thread.currentThread().getName()); }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } executorService.shutdown(); } List getList() { List list = new ArrayList(); for (int i = 0; i < MOCK_MAX; i++) { list.add("Version:JDK 8"); list.add("ThreadLocal"); list.add("Author:老王"); list.add("DateTime:" + LocalDateTime.now()); list.add("Test:ThreadLocal OOM"); } return list; } }
|
设置 JVM(Java 虚拟机)
启动参数 -Xmx100m
(最大运行内存 100 M
),运行程序不久后就会出现如下异常:
此时我们用 VisualVM
观察到程序运行的内存使用情况,发现内存一直在缓慢地上升直到内存超出最大值,从而发生内存溢出的情况。
内存使用情况,如下图所示:
# 内存溢出原理分析
在开始之前,先来看下 ThreadLocal
是如何存储数据的。
首先,找到 ThreadLocal.set()
的源码,代码如下:
1 2 3 4 5 6 7 8
| public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
|
可以看出 ThreadLocal
首先获取到 ThreadLocalMap 对象,然后再执行 ThreadLocalMap.set()
方法,进而打开此方法的源码,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private void set(ThreadLocal<?> key, Object value) { 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); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
|
从整个代码可以看出,首先 ThreadLocal
并不存储数据,而是依靠 ThreadLocalMap
来存储数据, ThreadLocalMap
中有一个
Entry
数组,每个 Entry
对象是以 K/V 的形式对数据进行存储的,其中 K
就是 ThreadLocal
本身,而 V
就是要存储的值,如下图所示:
可以看出:一个 Thread 中只有一个 ThreadLocalMap
,每个 ThreadLocalMap
中存有多个 ThreadLocal
, ThreadLocal
引用关系如下:
其中:实线代表强引用,虚线代表弱引用(弱引用具有更短暂的生命周期,在执行垃圾回收时,一旦发现只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存)。
看到这里我们就理解了 ThreadLocal
造成内存溢出的原因:如果 ThreadLocal 没有被直接引用(外部强引用),在 GC(垃圾回收)时,由于
ThreadLocalMap
中的 key
是弱引用,所以一定就会被回收,这样一来 ThreadLocalMap
中就会出现 key
为 null
的
Entry,并且没有办法访问这些数据,如果当前线程再迟迟不结束的话,这些 key
为 null
的 Entry
的 value
就会一直存在一条强引用链: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
并且永远无法回收,从而造成内存泄漏。
# 解决
切记 用完之后就 remove
掉!
# 最后
希望和你一起遇见更好的自己。
扫码或搜索:方家小白
发送 290992
即可立即永久解锁本站全部文章