【问题标题】:Java threads and garbage collector [duplicate]Java线程和垃圾收集器[重复]
【发布时间】:2012-04-30 08:01:59
【问题描述】:

可能重复:
Java Thread Garbage collected or not

考虑以下类:

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. 另一方面,这些实例是否会被 GC 删除 在 `run()' 中的线程结束之后,还是我们在浪费内存(“内存泄漏”)?

  3. 如果 1. 或 2. 有问题,那么正确的方法是什么?

谢谢

【问题讨论】:

  • 你实际上可以很容易地测试这个。
  • @Quaternion,是的,也许,但是另一个问题的措辞真的很“模糊”并且难以理解,恕我直言。
  • 这不是重复的。链接的问题没有解决“完成后是否会被垃圾收集”的问题(我正要专门问这个)。它只询问/回答“运行时不会被 GC”的方面。我认为这里的这个问题更好。
  • 当您将内容监管游戏化时会发生这种情况。显然不是重复的东西会被标记为重复。

标签: java multithreading garbage-collection


【解决方案1】:
  1. 活动线程引用的任何对象都不能被释放。
  2. 是的,实例将在 `run()' 中的线程结束后被 GC 删除。
  3. 没有问题。

【讨论】:

  • 请告诉我你是从哪里得出这个结论的。
【解决方案2】:
  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 被线程本身引用。
  • 从线程安全的角度来看,在构造函数中启动一个新线程也是一个糟糕的主意。在这种简单的情况下,可能会出现问题,但是如果您将类子类化并在子类构造函数中初始化了一些 final 字段,则最终可能会导致它们在被访问之前没有被初始化。
【解决方案3】:

在不指定线程组的情况下启动新线程将add it to the default group:

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

只要线程还活着,组就会保留对线程的引用,因此在此期间它不能被 GC。

当一个线程终止时(= 当run() 因任何原因返回时),该线程将从线程组中删除。这发生在从本机代码调用的私有方法exit() 中。这是对线程的最后一次引用丢失并且符合 GC 条件的时间点。

请注意,代码表明ThreadGroup 可以是null,但事实并非如此。各种空值检查只是为了在出现问题的罕见情况下避免 NPE。在Thread.init() 中,如果 Java 无法确定线程组,您将获得 NPE。

【讨论】:

  • 如果我们为在Foo 类中创建的所有Threads 创建一个新的ThreadGroup(其父级是默认的ThreadGroup)会怎样?它会对 GC 行为产生任何影响吗?特别是当创建 Foo 对象的类实例被 GCed,但创建的 Threads 仍在运行时。
  • @IrfanLatif 只要线程保持指向启动它的Foo 实例的指针(通过传递this),该实例只会在线程终止并且没有对此Foo 实例的其他引用。参考链变得多么复杂并不重要。就 GC 而言,ThreadGroup 并没有什么特别之处。线程只影响同步之类的事情。
【解决方案4】:
  1. Foo 对象被线程引用。线程在其运行期间一直被引用。因此它不会被垃圾回收。
  2. 没有内存泄漏。线程将结束并在此进程中被垃圾回收和Foo 对象。
  3. 应该可以正常工作。

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

    • Foo 对象不是在 run 方法中创建的,而是在 main 中创建的。
    • 只是另一个代码块,不会改变任何事情