论坛首页 Java企业应用论坛

关于线程的误用导致内存泄漏的讨论

浏览 12913 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2005-03-07  
前天去听Bea User Group的活动,有位兄弟谈起到自己遇到的一个内存泄漏问题,俺觉得可以讨论一下:
当时是出于一些特殊的需要,编写了一个线程类在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。
这是我的想法,后面一部分仅仅是猜测,现在贴出来,希望有人能够指正。
   发表时间: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观察观察,通过比较,也许会有一个结论。
0 请登录后投票
   发表时间:2005-03-07  
引用
私下里我和robbin谈起这个问题,不过他认为这是由于不正确的设计造成的——servlet中就不应该使用自己的线程


我确实觉得在App Server中自己启动用户线程是一个比较忌讳的事情,可能会导致意想不到的问题出现。再说本来Servlet已经提供了一个多线程并发的环境,你要做的就是把每个任务单独作为一个Servlet来运行就可以了,如果是异步任务,还有JMS这样的基础设施,我想像不出写用户线程的必要性。
0 请登录后投票
   发表时间:2005-03-07  
贴出代码来看看?
0 请登录后投票
   发表时间: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始终不释放的问题。
0 请登录后投票
   发表时间:2005-03-07  
robbin 写道
引用
私下里我和robbin谈起这个问题,不过他认为这是由于不正确的设计造成的——servlet中就不应该使用自己的线程


我确实觉得在App Server中自己启动用户线程是一个比较忌讳的事情,可能会导致意想不到的问题出现。再说本来Servlet已经提供了一个多线程并发的环境,你要做的就是把每个任务单独作为一个Servlet来运行就可以了,如果是异步任务,还有JMS这样的基础设施,我想像不出写用户线程的必要性。


robbin 写道
我觉得他那种情况可以把应用放在Windows下面跑一下,观察会不会有类似问题出现,另外在换成IBM JVM或者BEA的JRockit JVM观察观察,通过比较,也许会有一个结论。

就事论事当然是设计不当,不过我还想知道进一步的原因。
0 请登录后投票
   发表时间:2005-03-07  
如果是线程继承Thread,想对于实现Runnable来说,多了一个init方法,这又一次说明了接口比继承的依赖关系更小,呵呵。

start方法是native的,搞不清楚到底干了什么,我想如果要搞清楚,就得涉及看看JVM的sourcecode了,而且和具体操作系统的线程调度也会有一定的关系。
0 请登录后投票
   发表时间:2005-03-07  
代码代码
0 请登录后投票
   发表时间: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.
    }
}
0 请登录后投票
   发表时间: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。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics