锁定老帖子 主题:关于线程的误用导致内存泄漏的讨论
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2005-03-07
当时是出于一些特殊的需要,编写了一个线程类在servlet中使用,但是后来发现这个类的实例总是不能被 gc 回收,经过跟踪,发现是由于有一个地方没有启动线程,而只是利用了线程类的run方法完成业务功能,结果,凡是这样使用的类实例都挂到default thread group中,所以jvm不能回收。 私下里我和robbin谈起这个问题,不过他认为这是由于不正确的设计造成的——servlet中就不应该使用自己的线程,可我还有些怀疑,这个设计当然不好,但是也许还有别的问题,因为run 与start的区别与是否在servlet container中无关。由于自己对于thread掌握的不是很好,所以也不能肯定。 后来看了一下src,原来关键是这段代码: public synchronized native void start();; public Thread(); { init(null, null, "Thread-" + nextThreadNum();, 0);; } private void init(ThreadGroup g, Runnable target, String name, long stackSize); { Thread parent = currentThread();; if (g == null); { /* Determine if it's an applet or not */ SecurityManager security = System.getSecurityManager();; /* If there is a security manager, ask the security manager what to do. */ if (security != null); { g = security.getThreadGroup();; } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null); { g = parent.getThreadGroup();; } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess();; this.group = g; this.daemon = parent.isDaemon();; this.priority = parent.getPriority();; this.name = name.toCharArray();; this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = AccessController.getContext();; this.target = target; setPriority(priority);; if (parent.inheritableThreadLocals != null); this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);; /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; g.add(this);; } 也就是说,当你new一个线程对象的时候,是通过一个native方法得到它的相关信息并登记到线程组中,此时你的线程就已经有一个reference了,如果仅仅调用run方法,这是一个pure java method,所以没有什么附加的东西,线程组的reference还是没有取消,而如果你调用了start方法,它是一个native method,jvm就有可能为清理工作进行准备,等到stop或者run结束后,就会丢弃那个reference。 这样看来,native方法的工作有时候倒是有点像AOP,针对不同的OS写出不同的native方法,可以一致的解决线程回收之类的aspect。 这是我的想法,后面一部分仅仅是猜测,现在贴出来,希望有人能够指正。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2005-03-07
引用 如果仅仅调用run方法,这是一个pure java method,所以没有什么附加的东西,线程组的reference还是没有取消
这理由似乎还不充分吧。假设一个类如下: public class MyThread implements Runnable { public void run() { ...... } } 在Servlet中直接调用它如下: MyThread thread = new MyThread(); thread.run(); 这里并不涉及任何线程控制,根本就是一个pure java method,其实我还是想不出来有任何理由说明调用该类的Servlet线程无法回收。 我觉得他那种情况可以把应用放在Windows下面跑一下,观察会不会有类似问题出现,另外在换成IBM JVM或者BEA的JRockit JVM观察观察,通过比较,也许会有一个结论。 |
|
返回顶楼 | |
发表时间:2005-03-07
引用 私下里我和robbin谈起这个问题,不过他认为这是由于不正确的设计造成的——servlet中就不应该使用自己的线程
我确实觉得在App Server中自己启动用户线程是一个比较忌讳的事情,可能会导致意想不到的问题出现。再说本来Servlet已经提供了一个多线程并发的环境,你要做的就是把每个任务单独作为一个Servlet来运行就可以了,如果是异步任务,还有JMS这样的基础设施,我想像不出写用户线程的必要性。 |
|
返回顶楼 | |
发表时间:2005-03-07
贴出代码来看看?
|
|
返回顶楼 | |
发表时间:2005-03-07
robbin 写道 这理由似乎还不充分吧。假设一个类如下:
public class MyThread implements Runnable { public void run() { ...... } } 在Servlet中直接调用它如下: MyThread thread = new MyThread(); thread.run(); 这里并不涉及任何线程控制,根本就是一个pure java method,其实我还是想不出来有任何理由说明调用该类的Servlet线程无法回收。 这种情况与我说的不同,如果是使用继承Thread的方式(这应该是比较常见的情况),就会有父类构造器的干扰。所以才会有default thread group始终不释放的问题。 |
|
返回顶楼 | |
发表时间:2005-03-07
robbin 写道 引用 私下里我和robbin谈起这个问题,不过他认为这是由于不正确的设计造成的——servlet中就不应该使用自己的线程
我确实觉得在App Server中自己启动用户线程是一个比较忌讳的事情,可能会导致意想不到的问题出现。再说本来Servlet已经提供了一个多线程并发的环境,你要做的就是把每个任务单独作为一个Servlet来运行就可以了,如果是异步任务,还有JMS这样的基础设施,我想像不出写用户线程的必要性。 robbin 写道 我觉得他那种情况可以把应用放在Windows下面跑一下,观察会不会有类似问题出现,另外在换成IBM JVM或者BEA的JRockit JVM观察观察,通过比较,也许会有一个结论。
就事论事当然是设计不当,不过我还想知道进一步的原因。 |
|
返回顶楼 | |
发表时间:2005-03-07
如果是线程继承Thread,想对于实现Runnable来说,多了一个init方法,这又一次说明了接口比继承的依赖关系更小,呵呵。
start方法是native的,搞不清楚到底干了什么,我想如果要搞清楚,就得涉及看看JVM的sourcecode了,而且和具体操作系统的线程调度也会有一定的关系。 |
|
返回顶楼 | |
发表时间:2005-03-07
代码代码
![]() |
|
返回顶楼 | |
发表时间:2005-03-07
CafeBabe 写道 代码代码
![]() 这个问题不是来自我们的工作,所以原始的代码是没有的,我也是根据描述来推断,大致上应该是这样的代码: public class MyThread extends Thread{ public void run();{ //do something. } } public class MyServlet extends HttpServlet{ public void doGet(HttpServletRequest request, HttpServletResponse response); throw ServletException{ Thread t = new MyThread();; t.run();; //do another thing. } } |
|
返回顶楼 | |
发表时间:2005-03-07
如果你用new Thread()来创建Tread的实例,默认实现是将该Thread实例加入当前线程的ThreadGroup中,只要当前线程不被gc自然该Thread的实例也不会被gc。
另: 引用 public class MyThread extends Thread{
public void run(){ //do something. } } public class MyServlet extends HttpServlet{ public void doGet(HttpServletRequest request, HttpServletResponse response) throw ServletException{ Thread t = new MyThread(); t.run(); //do another thing. } } 此处thread 不会被回收我想情况应该是线程池的缘故,app server会把线程存入池中优化性能,在池中的线程是不会被gc的, servlet执行完毕后该servlet所在线程会被线程池回收,所以不会被gc,所以t 肯定也不会被gc。 |
|
返回顶楼 | |