对象中正在运行的线程是否阻止它在java中被垃圾收集?

时间:2009-01-26 17:15:51

标签: java concurrency garbage-collection

鉴于代码:

 new Thread(new BackgroundWorker()).start();

直观地说,在线程退出之前,感觉BackgroundWorker实例应该是安全的,但是这种情况是这样的吗?为什么?

修改

所有这些热量基本上是由我在同一篇文章中询问至少两个不同的问题而产生的。标题中的问题有一个答案,代码示例导致不同的方向 - 两个可能的结果取决于内联。

发布的答案真的很棒。我将授予Software Monkey绿色复选框。请注意,Darron的答案同样有效,但是Software Monkey解释了所遇到的问题;这个答案对我有用。

谢谢大家让这件事变得难忘;)

6 个答案:

答案 0 :(得分:12)

是的,因为GC只能收集任何线程无法访问的对象,并且Thread必须持有对它的runnable的引用(或者它无法调用它)。因此,很明显,在线程运行时可以访问Runnable对象。

无论执行所需的语义如何,在此新线程或其他任何线程无法再访问之前,您的对象将不会进行GC。至少足够长以调用Runnable的run(),并且如果该线程能够到达Runnable实例,则在线程的整个生命周期内,因此JVM规范保证您的构造是安全的。


编辑:因为Darron打败了这个人,有些人似乎对他的论点深信不疑,我将根据他的解释扩展我的解释。

  

假设除了Thread本身之外的任何人都不合法调用Thread.run(),

     

在这种情况下,Thread.run()的默认实现看起来是合法的:

void run() {
    Runnable tmp = this.myRunnable;  // Assume JIT make this a register variable.
    this.myRunnable = null;          // Release for GC.
    if(tmp != null) {
        tmp.run();         // If the code inside tmp.run() overwrites the register, GC can occur.
        }
    }

我认为在这种情况下,tmp仍然是对Thread.run()中执行的线程可以访问的runnable的引用,因此不符合GC的条件。

如果(出于某种莫名其妙的原因)代码如下所示:

void run() {
    Runnable tmp = this.myRunnable;  // Assume JIT make this a register variable.
    this.myRunnable = null;          // Release for GC.
    if(tmp != null) {
        tmp.run();         // If the code inside tmp.run() overwrites the register, GC can occur.
        System.out.println("Executed runnable: "+tmp.hashCode());
        }
    }

显然,当tmp.run()正在执行时,tmp引用的实例不能是GC'd。

我认为Darron错误地认为 reachable 只意味着那些可以通过追逐以所有Thread实例作为根的实例引用而找到的引用,而不是被定义为可以引用的引用由任何执行线程看到。要不然,或者我错误地认为相反。

此外,Darron可以假设JIT编译器进行了他喜欢的任何更改 - 编译器 允许更改执行代码的引用语义。如果我编写具有可访问引用的代码,则编译器无法优化该引用,并导致在该引用在范围内时收集我的对象。

我不知道实际可以找到的对象的细节;我只是推断我认为必须坚持的逻辑。如果我的推理不正确,那么在方法中实例化并且仅分配给该方法中的局部变量的任何对象将立即符合GC的条件 - 显然这不是也不可能。

此外,整个辩论都没有实际意义。如果唯一可到达的引用在Thread.run()方法中,因为runnable的run不引用它的实例,并且不存在对该实例的其他引用,包括传递给run()的隐式 this 方法(在字节码中,而不是声明的参数),然后是否收集对象实例无关紧要 - 按照定义,这样做不会造成任何伤害,因为如果隐含的话,不需要执行代码已经过优化。既然如此,即使Darron是正确的,最终的实际结果是由OP假定的结构是完全安全的。无论哪种方式。没关系。让我再重复一次,只是为了清楚 - 在最后的分析中无关紧要

答案 1 :(得分:5)

是的,这是安全的。原因并不像你想象的那么明显。

仅仅因为BackgroundWorker中的代码正在运行并不会使其安全 - 有问题的代码实际上可能不会引用当前实例的任何成员,从而允许优化“this”。

但是,如果仔细阅读java.lang.Thread类的run()方法的规范,您将看到Thread对象必须保留对Runnable的引用才能履行其合同。

编辑:因为我已经多次对这个答案进行了投票,我将扩展我的解释。

假设除了Thread本身之外的任何人都不合法调用Thread.run(),

在这种情况下,Thread.run()的默认实现看起来是合法的:

void run() {
    Runnable tmp = this.myRunnable;  // Assume JIT make this a register variable.
    this.myRunnable = null;          // Release for GC.
    if (tmp != null)
        tmp.run();         // If the code inside tmp.run() overwrites the register, GC can occur.
}

我一直在说的是, JLS中的任何内容都会阻止对象被垃圾收集,因为线程正在执行实例方法。这是使得最终确定变得如此困难的部分原因。

对于那些比我更了解它的人的详细信息,请参阅并发兴趣列表中的这个讨论thread

答案 2 :(得分:5)

这是安全的。 JVM保留对每个线程的引用。 Thread保持传递给其构造函数的Runnable实例。因此Runnable可以很容易地访问,并且不会在Thread的生命周期内收集。

我们知道由于Thread.run()的javadoc,Thread保存了对runnable的引用:

  

如果使用a构造此线程   然后单独运行Runnable运行对象   Runnable对象的run方法是   所谓的;否则,这种方法可以   什么也没有回来。

答案 3 :(得分:1)

是的,因为Thread在内部保留了对Runnable的引用(毕竟它是has to know what to run)。

答案 4 :(得分:1)

我愿意打赌,JVM包含对每个活动的线程对象的引用,或者可以在其根集中进行调度,但是我没有这个规范来确认这一点。

答案 5 :(得分:0)

不,我认为这不安全。

在实践中,你几乎肯定会逃脱它。但是,Java内存模型令人惊讶。事实上,上周才在JMM邮件列表上讨论了添加“保持对象存活”的方法的计划。目前,终结者可以在没有成员方法执行的先发生关系的情况下运行。目前,您在技术上需要通过在整个地方进行同步或在每个方法的末尾写一些易失性并在终结器中读取该易失性来引入先发生的实现。

正如Darron指出的那样,如果您可以获取Thread对象(例如通过Thread.enumerate),那么您可以在其上调用run,调用Runnablerun。但是,我仍然认为之前发生

我的建议:不要试图太“聪明”。