码迷,mamicode.com
首页 > 其他好文 > 详细

ThreadLocal深入研究

时间:2015-04-09 08:50:42      阅读:218      评论:0      收藏:0      [点我收藏+]

标签:threadlocal   memory leak   permgen leak   strong reference   weak reference   

不久前我写过一篇关于ThreadLocal用法的文章,但最近项目上出现了Memory Leak,调查后发现可能与ThreadLocal的使用有关,在此对ThreadLocal的使用作一些补充。


在ThreadLocal内部,其实是通过一个Map(类似Map<Thread, Object>)来保存各个线程独立的变量的,但是这个map有一点特殊,它对线程的引用是弱引用WeakReference(如果一个对象只被弱引用相联,那么GC就可以回收这个对象),这说明当线程执行结果后,即使没有显式的调用ThreadLocal.remove方法,GC也可以回收该线程在ThreadLocal中存放的独立对象了。

我们先看一个简单的例子:

public class App {
	public static void main(String[] args) throws Exception {
		final ThreadLocal<Obj> local = new ThreadLocal<Obj>();
		
		Thread t = new Thread() {
			public void run() {
				local.set(new Obj());
			}
		};
		t.start();
		
		while(true) {
			System.gc();
			TimeUnit.SECONDS.sleep(1);
		}
	}
}

class Obj {
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println(this + " finalized.");
	}
}
线程t开始执行时创建了一个Obj对象,随后把该对象放入ThreadLocal中,之后线程t执行结束。之后便会输出Obj@721cdeff finalized,说明Obj对象被GC回收,这与我们上面的分析是一致的。

我们对程序稍作修改,再来看看:

public class App {
	public static void main(String[] args) throws Exception {
		final ThreadLocal<Obj> local = new ThreadLocal<Obj>();
		ExecutorService exec = Executors.newFixedThreadPool(2);
		
		Thread t = new Thread() {
			public void run() {
				local.set(new Obj());
			}
		};
		exec.execute(t);
		
		while(true) {
			System.gc();
			TimeUnit.SECONDS.sleep(1);
		}
	}
}

class Obj {
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println(this + "finalized.");
	}
}
与之前的例子不同的地方是这里使用了线程池来执行线程,这样当线程执行完后并没有被销毁,而是还给了线程池。正因为此ThreadLocal Map中为该线程保存的entry不会被GC回收,也就是说上面这个例子不会有任何输出,Obj对象会在Heap中一直存在。

可以想象下在一个web server环境下,为了提高对请求的响应,大部分web server(比如tomcat)都是预先创建一个线程池。当有请求到来时,就从线程池中取出一个线程来处理请求,之后再将线程放回线程池,也就是说这些线程至始至终都不会被销毁。那如果像上面的例子一样在Web环境下错误地使用了ThreadLocal会带来什么后果呢?

我们再看一个例子:

public class App {
	public static void main(String[] args) throws Exception {
		final ThreadLocal<Object> local = new ThreadLocal<Object>();
		ExecutorService exec = Executors.newFixedThreadPool(2);
		
		Thread t = new Thread() {
			public void run() {
				local.set(App.createObj());
			}
		};
		exec.execute(t);
		
		while(true) {
			System.gc();
			TimeUnit.SECONDS.sleep(1);
		}
	}
	
	public static Object createObj()  {
		try {
			CustomClassLoader cl = 
					new CustomClassLoader(new URL("file:///Users/ouyang/Develop/eclipse/workspace/Test/bin/"));
			
			Class<?> clazz = cl.loadClass("App$Obj");
	        return clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	public static class Obj {
		@Override
		protected void finalize() throws Throwable {
			super.finalize();
			System.out.println(this + "finalized.");
		}
	}
}

class CustomClassLoader extends URLClassLoader {

    public CustomClassLoader(URL... urls) {
        super(urls, null);
    }

    @Override
    protected void finalize() {
        System.out.println("*** CustomClassLoader finalized!");
    }
}
这个例子在之前例子的基础上,修改了Obj对象的创建,这次我们使用一个自定义的ClassLoader来加载和创建Obj对象。同样的,这个例子不会有任何的输出,Obj对象不能被GC回收,从而导致加载他的CustomClassLoader对象不能被回收,更要命的是其它被CustomClassLoader加载的类啊、静态数据对象等等,都不能被GC回收,甚至是在undeploy应用的时候都不能被回收。只要web server不重启,每一次重新布暑应用都将加大这些无效类、静态数据所占用的空间。从而造成Permgen Leak和Memory Leak。


所以,必须在线程执行结束前,调用ThreadLocal的remove方法显式的删除对独立对象的强引用。

ThreadLocal深入研究

标签:threadlocal   memory leak   permgen leak   strong reference   weak reference   

原文地址:http://blog.csdn.net/oyl822/article/details/44949173

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!