Java线程和垃圾回收器

24

可能是重复问题:
Java线程是否会被垃圾回收

考虑以下类:

class Foo implements Runnable {

  public Foo () {
       Thread th = new Thread (this);
       th.start();
  }

  public run() {
    ... // long task
  }

}
如果我们通过进行以下操作创建多个Foo的实例:
new Foo();
new Foo();
new Foo();
new Foo();

(请注意,我们不会保留它们的指针。)

  1. 在线程中的 run() 方法结束之前,这些实例能否被垃圾回收器删除?(换句话说,这些 Foo 对象是否有引用关系?)

  2. 另一方面,在线程中的 run() 方法结束后,这些实例是否会被垃圾回收器删除,或者我们正在浪费内存(出现 "内存泄漏")?

  3. 如果问题出现在 1 或 2 中,正确的解决方式是什么?

谢谢


你可以非常容易地测试这个。 - Radu Murzea
@Quaternion,也许是这样,但我认为那个问题的措辞真的很“晦涩难懂”。 - cibercitizen1
1
当你将内容审查变成游戏化的时候,就会出现这种情况。明明不是重复内容,却被标记为重复。 - Joseph Persico
5个回答

23
  1. 被活动线程引用的任何对象都不得被释放。
  2. 是的,在`run()'方法结束后,实例将由GC移除。
  3. 没问题。

1
请告诉我你是从哪里得出这个结论的。 - Chao

12
  1. 在run()方法结束之前,这些实例可以被垃圾回收器清除吗?(换句话说:这些Foo对象有没有任何引用?)

不会。当构造函数运行时,GC不会收集对象。否则,甚至最简单的情况也一样:

Customer c = new Customer();

在构造函数 Customer 运行时可能会失败。另一方面,当您启动新线程时,线程对象成为新的 GC 根,因此由该对象引用的所有内容都不会成为垃圾回收的对象。

  1. 另一方面,在 `run()'结束后,这些实例是否会被 GC 删除,或者我们浪费了内存("内存泄漏")?

一旦线程完成,它就不再是 GC 根。如果没有其他代码指向该线程对象,则它将被垃圾回收。

  1. 如果问题1或2是问题,正确的做法是什么?

您的代码非常好。但是:

  • 从单元测试的角度来看,在构造函数中启动新线程是一个糟糕的想法。

  • 保留对所有运行线程的引用可能是有益的,例如,如果您想稍后中断这些线程。


@MarkoTopolnik:哦,它确实有用!OP将“this”传递给“Thread”构造函数,这恰好是“Foo”的一个实例。然而,这是正确的 - 如果“Foo”具有某些状态,则所有状态变量都将幸存于GC,因为它们由“Foo”引用 - 而“Foo”被线程本身引用。 - Tomasz Nurkiewicz
4
从线程安全的角度来看,在构造函数中启动新线程是一个糟糕的主意。在这种简单情况下,可能会出现问题,但如果你派生了这个类并在子类构造函数中初始化一些 final 字段,你可能会在访问它们之前没有初始化它们。 - Bill Michell

8

如果在没有指定线程组的情况下启动一个新线程,它将会被添加到默认组

如果组为 null 并且存在安全管理器,则该组由安全管理器的 getThreadGroup 方法确定。如果组为 null 并且不存在安全管理器,或者安全管理器的 getThreadGroup 方法返回 null,则该组将被设置为与创建新线程的线程相同的 ThreadGroup。

只要线程存活,该组就会保留对该线程的引用,因此在此期间无法进行 GC。

当线程终止(即出于任何原因 run() 返回)时,线程将从线程组中移除。这在从本机代码调用的私有方法 exit() 中发生。这是失去对线程的最后引用并使其有资格进行 GC 的时间点。

请注意,该代码指示ThreadGroup可能为null,但实际上并非如此。各种空检查只是为了避免在某些情况下出现NPE。在Thread.init()中,如果Java无法确定线程组,则会出现NPE。

如果我们为 Foo 类中创建的所有 Thread 创建一个新的 ThreadGroup(其父级是默认的 ThreadGroup),会对 GC 行为产生什么影响?特别是在创建了 Foo 对象的类实例被 GC 回收时,但创建的 Thread 仍在运行时。 - Irfan Latif
只要线程保持对启动它的 Foo 实例的指针(通过传递 this),该实例仅在线程终止且没有对此 Foo 实例的其他引用后才会被 GC 回收。无论引用链变得多么复杂,都不重要。就垃圾回收而言,ThreadGroup 并没有什么特别之处。线程只影响同步等事项。 - Aaron Digulla

3
  1. Foo 对象被线程引用,线程在运行期间一直被引用,因此它不会被垃圾回收。
  2. 没有内存泄漏。线程将结束并被垃圾回收,其中包括 Foo 对象。
  3. 它应该可以正常工作。

0

假设您正在run方法中创建对象,当run方法退出时,这些对象将超出范围,然后可供垃圾回收使用。Run只是另一种方法。在这里使用线程与否不会以任何方式改变垃圾回收行为。您需要关心的是对象何时超出范围,通常与块范围(方法块、while循环、if块等)相关。

因此,由于您一开始没有保留任何对该对象的引用,因此您可能希望将创建对象的逻辑提取到其自己的短生命周期方法中。这样创建的对象就不需要保留超出该方法范围之外。


Foo对象不是在run方法中创建的,而是例如在main方法中创建的。 - cibercitizen1
只是另一个代码块,没有改变任何事情。 - Jilles van Gurp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接