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

时间:2021-11-08 03:05:27

Given the code:

鉴于代码:

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

Intuitively it feels like the BackgroundWorker instance should be safe from GC until the thread exits, but is this the case ? And why ?

直观地说,感觉像BackgroundWorker实例应该是GC安全的,直到线程退出,但情况如此?为什么?

Edit:

All this heat is basically generated by me asking at least two different questions in the same post. The question in the title has one answer, the code sample leads in a different direction - with two possible outcomes depending on inlining.

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

The answers posted are really excellent. I will be awarding Software Monkey the green checkbox. Please note that Darron's answer is equally valid, but Software Monkey explained the problem I was having; it was the answer that worked for me.

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

Thank you all for making this a memorable affair ;)

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

6 个解决方案

#1


Yes, because GC can only collect objects not reachable by any thread, and Thread must hold a reference to it's runnable (or it would not be able to invoke it). So, clearly, your Runnable object is reachable while your thread is running.

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

Regardless of the semantics required for execution, your object will not be GC'd until it is no longer reachable by this new thread or any other; that will be at least long enough to invoke your Runnable's run(), and for the entire life of the thread if that thread is able to reach the Runnable instance, so your construct is guaranteed to be safe by the JVM specification.

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


EDIT: Because Darron is beating this to death, and some seem convinced by his argument I'm going to expand upon my explanation, based on his.

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

Assume for the moment that it was not legal for anyone except Thread itself to call Thread.run(),

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

In that case it would be legal for the default implementation of Thread.run() to look like:

在这种情况下,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.
        }
    }

I contend that in this case tmp is still a reference to the runnable reachable by the thread executing within Thread.run() and therefore is not eligible for GC.

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

What if (for some inexplicable reason) the code looked like:

如果(由于一些莫名其妙的原因)代码看起来像:

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());
        }
    }

Clearly, the instance referred to by tmp cannot be GC'd while tmp.run() is executing.

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

I think Darron mistakingly believes that reachable means only those references which can be found by chasing instance references starting with all Thread instances as roots, rather than being defined as a reference which can be seen by any executing thread. Either that, or I am mistaken in believing the opposite.

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

Further, Darron can assume that the JIT compiler makes any changes he likes - the compiler is not permitted to change the referential semantics of the executing code. If I write code that has a reachable reference, the compiler cannot optimize that reference away and cause my object to be collected while that reference is in scope.

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

I don't know the detail of how reachable objects are actually found; I am just extrapolating the logic which I think must hold. If my reasoning were not correct, then any object instantiated within a method and assigned only to a local variable in that method would be immediately eligible for GC - clearly this is not and can not be so.

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

Furthermore, the entire debate is moot. If the only reachable reference is in the Thread.run() method, because the runnable's run does not reference it's instance and no other reference to the instance exists, including the implicit this passed to the run() method (in the bytecode, not as a declared argument), then it doesn't matter whether the object instance is collected - doing so, by definition, can cause no harm since it's not needed to execute the code if the implicit this has been optimized away. That being the case, even if Darron is correct, the end practical result is that the construct postulated by the OP is perfectly safe. Either way. It doesn't matter. Let me repeat that one more time, just to be clear - in the end analysis it doesn't matter.

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

#2


Yes, it is safe. The reason why is not as obvious as you might think.

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

Just because code in BackgroundWorker is running does not make it safe -- the code in question may not actually reference any members of the current instance, allowing "this" to be optimized away.

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

However, if you carefully read the specification for the java.lang.Thread class's run() method you'll see that the Thread object must keep a reference to the Runnable in order to fulfill its contract.

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

EDIT: because I've been voted down several times on this answer I'm going to expand upon my explanation.

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

Assume for the moment that it was not legal for anyone except Thread itself to call Thread.run(),

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

In that case it would be legal for the default implementation of Thread.run() to look like:

在这种情况下,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.
}

What I keep saying is that nothing in the JLS prevents an object from being garbage collected just because a thread is executing an instance method. This is part of what makes getting finalization correct so hard.

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

For excruciating detail on this from people who understand it much better than I do, see this discussion thread from the concurrency interest list.

对于那些比我更了解它的人来说非常难以理解的细节,请参阅并发兴趣列表中的讨论主题。

#3


It is safe. The JVM holds onto a reference to each thread. The Thread holds on to an instance of the Runnable passed into its constructor. So the Runnable is strongly reachable, and will not be collected for the life of the Thread.

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

We know that the Thread holds a reference to the runnable because of the javadoc for Thread.run():

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

If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.

如果使用单独的Runnable运行对象构造此线程,则调用该Runnable对象的run方法;否则,此方法不执行任何操作并返回。

#4


Yes, because the Thread keeps a reference to the Runnable internally (it has to know what to run, after all).

是的,因为Thread在内部保留了对Runnable的引用(毕竟它必须知道要运行什么)。

#5


I am willing to bet that the JVM includes a reference to each thread object that is active or can be scheduled in its root set, but I don't have the spec with me to confirm this.

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

#6


No, I don't think it is not safe.

不,我认为这不安全。

In practice you will almost certainly get away with it. However, the Java Memory Model is surprising. Indeed there was only last week discussion on the JMM mailing list about plans to add a method to "keep objects alive". Currently the finaliser can be run without a happens-before relationship from the execution of a member method. At the moment, you technically need to introduce a happens-before realitionship by synchronising all over the place or writing some volatile at the end of each method and a read of that volatile in the finaliser.

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

As Darron points out, if you can get of the Thread object (through Thread.enumerate for instance) then you can call run on it, which calls the Runnable's run. However, I still don't think there is a happens-before in there.

正如Darron指出的那样,如果你可以得到Thread对象(例如通过Thread.enumerate),那么你就可以调用run,它调用Runnable的运行。但是,我仍然认为之前没有发生过。

My advice: Don't try to be too "clever".

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

#1


Yes, because GC can only collect objects not reachable by any thread, and Thread must hold a reference to it's runnable (or it would not be able to invoke it). So, clearly, your Runnable object is reachable while your thread is running.

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

Regardless of the semantics required for execution, your object will not be GC'd until it is no longer reachable by this new thread or any other; that will be at least long enough to invoke your Runnable's run(), and for the entire life of the thread if that thread is able to reach the Runnable instance, so your construct is guaranteed to be safe by the JVM specification.

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


EDIT: Because Darron is beating this to death, and some seem convinced by his argument I'm going to expand upon my explanation, based on his.

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

Assume for the moment that it was not legal for anyone except Thread itself to call Thread.run(),

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

In that case it would be legal for the default implementation of Thread.run() to look like:

在这种情况下,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.
        }
    }

I contend that in this case tmp is still a reference to the runnable reachable by the thread executing within Thread.run() and therefore is not eligible for GC.

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

What if (for some inexplicable reason) the code looked like:

如果(由于一些莫名其妙的原因)代码看起来像:

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());
        }
    }

Clearly, the instance referred to by tmp cannot be GC'd while tmp.run() is executing.

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

I think Darron mistakingly believes that reachable means only those references which can be found by chasing instance references starting with all Thread instances as roots, rather than being defined as a reference which can be seen by any executing thread. Either that, or I am mistaken in believing the opposite.

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

Further, Darron can assume that the JIT compiler makes any changes he likes - the compiler is not permitted to change the referential semantics of the executing code. If I write code that has a reachable reference, the compiler cannot optimize that reference away and cause my object to be collected while that reference is in scope.

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

I don't know the detail of how reachable objects are actually found; I am just extrapolating the logic which I think must hold. If my reasoning were not correct, then any object instantiated within a method and assigned only to a local variable in that method would be immediately eligible for GC - clearly this is not and can not be so.

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

Furthermore, the entire debate is moot. If the only reachable reference is in the Thread.run() method, because the runnable's run does not reference it's instance and no other reference to the instance exists, including the implicit this passed to the run() method (in the bytecode, not as a declared argument), then it doesn't matter whether the object instance is collected - doing so, by definition, can cause no harm since it's not needed to execute the code if the implicit this has been optimized away. That being the case, even if Darron is correct, the end practical result is that the construct postulated by the OP is perfectly safe. Either way. It doesn't matter. Let me repeat that one more time, just to be clear - in the end analysis it doesn't matter.

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

#2


Yes, it is safe. The reason why is not as obvious as you might think.

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

Just because code in BackgroundWorker is running does not make it safe -- the code in question may not actually reference any members of the current instance, allowing "this" to be optimized away.

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

However, if you carefully read the specification for the java.lang.Thread class's run() method you'll see that the Thread object must keep a reference to the Runnable in order to fulfill its contract.

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

EDIT: because I've been voted down several times on this answer I'm going to expand upon my explanation.

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

Assume for the moment that it was not legal for anyone except Thread itself to call Thread.run(),

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

In that case it would be legal for the default implementation of Thread.run() to look like:

在这种情况下,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.
}

What I keep saying is that nothing in the JLS prevents an object from being garbage collected just because a thread is executing an instance method. This is part of what makes getting finalization correct so hard.

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

For excruciating detail on this from people who understand it much better than I do, see this discussion thread from the concurrency interest list.

对于那些比我更了解它的人来说非常难以理解的细节,请参阅并发兴趣列表中的讨论主题。

#3


It is safe. The JVM holds onto a reference to each thread. The Thread holds on to an instance of the Runnable passed into its constructor. So the Runnable is strongly reachable, and will not be collected for the life of the Thread.

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

We know that the Thread holds a reference to the runnable because of the javadoc for Thread.run():

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

If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.

如果使用单独的Runnable运行对象构造此线程,则调用该Runnable对象的run方法;否则,此方法不执行任何操作并返回。

#4


Yes, because the Thread keeps a reference to the Runnable internally (it has to know what to run, after all).

是的,因为Thread在内部保留了对Runnable的引用(毕竟它必须知道要运行什么)。

#5


I am willing to bet that the JVM includes a reference to each thread object that is active or can be scheduled in its root set, but I don't have the spec with me to confirm this.

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

#6


No, I don't think it is not safe.

不,我认为这不安全。

In practice you will almost certainly get away with it. However, the Java Memory Model is surprising. Indeed there was only last week discussion on the JMM mailing list about plans to add a method to "keep objects alive". Currently the finaliser can be run without a happens-before relationship from the execution of a member method. At the moment, you technically need to introduce a happens-before realitionship by synchronising all over the place or writing some volatile at the end of each method and a read of that volatile in the finaliser.

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

As Darron points out, if you can get of the Thread object (through Thread.enumerate for instance) then you can call run on it, which calls the Runnable's run. However, I still don't think there is a happens-before in there.

正如Darron指出的那样,如果你可以得到Thread对象(例如通过Thread.enumerate),那么你就可以调用run,它调用Runnable的运行。但是,我仍然认为之前没有发生过。

My advice: Don't try to be too "clever".

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