昨天看到tf因为threadlocal用完没有清理而导致OOM
实验一:
package main;
/**
* User: zhangyangsheng
* Date: 13-1-24
* Time: 下午6:40
*/
public class ThreadLocalTest {
ThreadLocal<Value> threadLocalPart = new ThreadLocal<Value>();
static class Value{
final int i;
Value(int i){
this.i = i;
}
}
ThreadLocalTest setThreadVal(int i){
threadLocalPart.set(new Value(i));
return this;
}
int getThreadVal(){
return threadLocalPart.get().i;
}
public static void main(String[] args) {
int sum = 0;
for(int i = -500000000;i<=500000000;i++){
sum+= new ThreadLocalTest().setThreadVal(i).getThreadVal();
}
System.out.println(sum);
}
}
对应的GC
可以看到GC非常频繁,但并未出现OOM
实验二:调整get的算法,在get后把threadLocal清理掉
int getThreadVal(){
int temp = threadLocalPart.get().i;
threadLocalPart.remove();
return temp;
}
调整后GC非常平稳,相比之前少了很多
实验三
把get方法恢复为实验一的情况,把内部类的static修饰符去除
结果:
java.lang.OutOfMemoryError
- klass: 'java/lang/OutOfMemoryError'
#
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (exceptions.cpp:390), pid=8494, tid=4620660736
# fatal error: ExceptionMark destructor expects no pending exceptions
#
# JRE version: 7.0_04-b21
# Java VM: Java HotSpot(TM) 64-Bit Server VM (23.0-b21 mixed mode bsd-amd64 compressed oops)
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /Users/zhangyangsheng/opensource/NettyExample/hs_err_pid8494.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.sun.com/bugreport/crash.jsp
#
Process finished with exit code 134
由于OutOfMemory导致了虚拟机crash
原因分析:
1.由于ThreadLocalMap使用的Entry是继承自WeakReference,所以当该WeakReference不存在强引用时会被GC掉
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
并且ThreadLocal在set的时候在key为null的时候会对value和entry进行清理
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
所以结果一比结果二进行GC频繁的原因是虚拟机要GC掉一部分WeakReference(Entry.key)来存放新申请的对象。
那么为什么实验三会导致OOM呢,由于非静态内部类会持有创建这个类的外部的引用,导致WeakReference存在一个强引用而不能被GC掉。
图示:
GC后,由于key是WeakReference,且不存在强引用,所以被垃圾回收
向ThreadLocal中设置值时,发现key为null时会吧value清空
最后该entry会被覆盖掉,内存成功回收
在非静态内部类的情况下应用是这样的
由于entry的value是非静态内部类,所以存在该内部类所对应的外部类的引用(ThreadLocalTest),而外部类又存在对ThreadLocal的强引用,导致key无法回收,key无法回收又带来value无法回收最终导致ThreadLocalMap被撑满。