public class TestThreadLocal {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> test("abc", false));
t.start();
t.join();
System.out.println("----gc后----");
Thread t2 = new Thread(() -> test("def", true));
t2.start();
t2.join();
}
@SneakyThrows
private static void test(String s, boolean isGc) {
new ThreadLocal<>().set(s);
if (isGc) {
System.gc();
}
Thread t = Thread.currentThread();
Class<? extends Thread> clz = t.getClass();
Field field = clz.getDeclaredField("threadLocals");
field.setAccessible(true);
Object threadLocalMap = field.get(t);
Class<?> tlmClass = threadLocalMap.getClass();
Field tableField = tlmClass.getDeclaredField("table");
tableField.setAccessible(true);
Object[] arr = (Object[]) tableField.get(threadLocalMap);
for (Object o : arr) {
if (o != null) {
Class<?> entryClass = o.getClass();
Field valueField = entryClass.getDeclaredField("value");
Field referentField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
valueField.setAccessible(true);
referentField.setAccessible(true);
Object value = valueField.get(o);
Object referent = referentField.get(o);
System.out.println(String.format("弱引用:%s, 值:%s", referent, value));
}
}
}
}
弱引用:java.lang.ThreadLocal@71442059, 值:abc
弱引用:java.lang.ThreadLocal@5dce77fa, 值:java.lang.ref.SoftReference@20789f2d
----gc后----
弱引用:null, 值:def
/**
* 线程池中的线程被复用,threadLocal中的值也会被复用,所以每次用完线程之后都要手动remove该threadLocal
* @author xuleyan
* @version TestPool.java, v 0.1 2021-07-30 8:16 下午
*/
public class TestPool {
public static ThreadLocal<Integer> valueHolder = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static int getValue() {
return valueHolder.get();
}
public static void remove() {
valueHolder.remove();
}
public static void increment() {
valueHolder.set(valueHolder.get() + 1);
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.execute(() -> {
try {
long threadId = Thread.currentThread().getId();
int before = getValue();
increment();
int after = getValue();
System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after);
} finally {
remove();
}
});
}
executorService.shutdown();
}
}
1.finally中添加 remove()
threadId: 10, before: 0, after: 1
threadId: 11, before: 0, after: 1
threadId: 11, before: 0, after: 1
threadId: 10, before: 0, after: 1
threadId: 12, before: 0, after: 1
2.finally中不使用 remove()
threadId: 10, before: 0, after: 1
threadId: 11, before: 0, after: 1
threadId: 11, before: 1, after: 2
threadId: 10, before: 1, after: 2
threadId: 12, before: 0, after: 1
// -Xms128m -Xmx128m -Xmn64M -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
// 打印gc信息,并设置内存限制快速内存溢出
public class TestThreadLocalOom {
public static final Integer BIG_LOOP = 10000;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 500; i ++) {
executorService.execute(() -> {
ThreadLocal<List<User>> threadLocal = new ThreadLocal<>();
threadLocal.set(new TestThreadLocalOom().addBigList());
System.out.println("ThreadId:" + Thread.currentThread().getName() + "addBigList");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
// threadLocal.remove();
});
}
Thread.sleep(10000);
executorService.shutdown();
}
public List<User> addBigList() {
List<User> params = new ArrayList<>(BIG_LOOP);
for (int i = 0; i<BIG_LOOP; i++) {
params.add(new User("haha", 28));
}
return params;
}
}
(1)注释掉remove(), 就会出现疯狂的full gc, 最终会出现oom
PSYoungGen: 49152K->49152K(57344K) 新生代100%
ParOldGen: 65320K->65279K(65536K)。 老年代100%
114472K->114431K(122880K)。 总的堆空间100%挤满
Metaspace: 6067K->6067K(1056768K)。 元空间100%
46.314: [Full GC (Ergonomics) java.lang.OutOfMemoryError: GC overhead limit exceeded
[PSYoungGen: 49152K->49152K(57344K)] [ParOldGen: 65320K->65279K(65536K)] 114472K->114431K(122880K), [Metaspace: 6067K->6067K(1056768K)], 0.1804484 secs] [Times: user=0.59 sys=0.01, real=0.18 secs]
46.495: [Full GC (Ergonomics) [PSYoungGen: 49152K->49152K(57344K)] [ParOldGen: 65281K->65280K(65536K)] 114433K->114432K(122880K), [Metaspace: 6067K->6067K(1056768K)], 0.1546160 secs] [Times: user=0.49 sys=0.00, real=0.16 secs]
(2)不注释remove()方法,几乎没有fullgc, 且堆空间占用不高,说明大部分线程中的threadLocalMap中的entry都被回收了。
Heap
PSYoungGen total 57344K, used 15858K [0x00000007bc000000, 0x00000007c0000000, 0x00000007c0000000)
eden space 49152K, 32% used [0x00000007bc000000,0x00000007bcf7cb40,0x00000007bf000000)
from space 8192K, 0% used [0x00000007bf000000,0x00000007bf000000,0x00000007bf800000)
to space 8192K, 0% used [0x00000007bf800000,0x00000007bf800000,0x00000007c0000000)
ParOldGen total 65536K, used 8259K [0x00000007b8000000, 0x00000007bc000000, 0x00000007bc000000)
object space 65536K, 12% used [0x00000007b8000000,0x00000007b8810f60,0x00000007bc000000)
Metaspace used 6136K, capacity 6382K, committed 6400K, reserved 1056768K
class space used 684K, capacity 765K, committed 768K, reserved 1048576K