【问题标题】:When thread in Java is removed from memory? [duplicate]Java中的线程何时从内存中删除? [复制]
【发布时间】:2015-04-03 20:55:51
【问题描述】:

来自 Java API 文档:

Java 虚拟机继续执行线程,直到以下 发生:

所有不是守护线程的线程都已经死亡,要么通过返回 从调用 run 方法或抛出异常 传播到 run 方法之外。

我希望我的假设是正确的,即一旦线程完成其run() 方法,它就有资格进行垃圾收集。在同样的情况下,我只是想知道:

  1. 如果它在返回后不符合垃圾回收条件 来自run(),是否应该将其引用设置为null 来做到这一点?
  2. 有资格进行垃圾收集并不一定意味着 该对象将从内存中删除。它完全由 垃圾收集时的底层操作系统/JVM。 但是如何确保(通过 Java 程序或外部工具)对象已完全从 记忆?
  3. 如果一个线程在完成它的 run() 方法后就被认为是死的,为什么 我还能在上面执行isAlive()getState() 同一个线程对象?两个调用都返回 falseRUNNABLE 分别。

【问题讨论】:

    标签: java memory memory-management garbage-collection jvm


    【解决方案1】:

    Thread 类是本机内存中真实线程的代理。

    我希望我的假设是正确的,一旦线程完成它的 run() 方法,它就有资格进行垃圾回收。

    run()后面其实有一段代码,这段代码处理未捕获的异常。

    一旦线程死亡,它的本机内存和堆栈会立即释放,无需 GC。然而,Thread 对象就像任何其他对象一样,它一直存在,直到 GC 决定它可以是免费的,例如没有强烈的引用。

    同样,FileOutputStream 是操作系统中文件的代理。即使文件已被close() 或什至删除,您仍然可以引用该对象。

    如果它在从 run() 返回后没有资格进行垃圾回收,是否应该将其引用设置为 null 来做到这一点?

    您很少需要在任何地方执行此操作。事实上,首先不保留对线程的引用,或者使用 ExecutorService 来管理线程通常更简单。

    当我有一个具有Thread 字段的对象时,我经常在线程终止时使该对象终止,因此该字段不需要nulled。

    我还使用用于 Fork/Join 的内置线程池。这是一种在后台线程中执行任务的更轻量级的方式,因为它不会创建和销毁线程。

    ExecutorService fjp = ForkJoinPool.commonPool();
    

    符合垃圾回收条件并不一定意味着该对象将从内存中删除。垃圾收集时由底层操作系统/JVM自行决定。但是如何确保(通过 Java 程序或外部工具)对象已完全从内存中删除?

    您不能也通常不应该尝试。 GC 会在需要时清理资源。

    如果一个线程在完成它的 run() 方法后就被认为是死的,为什么我仍然可以在同一个线程对象上执行 isAlive() 或 getState()?这两个调用分别返回 false 和 RUNNABLE。

    线程对象与任何其他对象一样。只要您持有对它的引用,就可以对其调用方法。

    【讨论】:

      【解决方案2】:

      您似乎遇到了 Java 中不理解线程的常见陷阱:线程本身不是 Java 对象。它是本机资源(执行线程)。一旦运行完代码,它将被“从内存中删除”。

      另一方面,Thread 的实例只是一个普通的 Java 对象,它与任何其他对象具有相同的生命周期——除了一个额外的规则,即至少只要底层本机线程处于活动状态,它就保持可访问性.您可以随时致电Thread.currentThread()这一事实暗示了这一点。

      因此,如果您保留对负责死线程的Thread 实例的引用,它不会神奇地消失,并且它的所有方法将继续按指定操作。您可以根据需要坚持多久。

      关于您的问题 2,从内存中“删除”一个对象实际上是一个毫无意义的术语。运行时本身实际上不知道收集的对象——它们是它忘记的对象。

      【讨论】:

        【解决方案3】:

        线程已完成执行的事实不会改变对对象的引用的有效性。当您持有该引用时,不能对 Thread 对象进行垃圾回收。

        这意味着当您持有对线程的引用时,您可以调用诸如isAlive 之类的东西。当您释放所有引用时,线程可能会被垃圾回收。

        如果您希望引用一个对象而不阻止该对象被垃圾回收,那么您必须使用WeakReference。当您使用此类引用时,您必须检查所引用的对象是否仍然存在。

        【讨论】:

          【解决方案4】:

          关于线程有两点需要考虑。

          1. 在运行之前和期间运行线程对象本身就是 GC 根,即:它们和任何可从它们强访问的对象都不能被收集。
          2. 一旦线程完成运行,它就会“恢复”为普通对象。与任何其他对象相同的 GC 规则适用于它们。因此,例如,当您对它们有很强的引用时,它们不会被收集,是的,您可以毫无困难地调用它们的方法。

          所以我的建议是像对待 ListMap 类型的对象一样对待“死”线程。适用于它们的内容适用于已完成运行的线程。

          【讨论】:

          • 投票支持 GC 根
          【解决方案5】:

          如果它在从 run() 返回后没有资格进行垃圾回收,是否应该将其引用设置为 null 来做到这一点?

          明确将对象设置为 null(如果您确定将来不会使用它们)总是更好的做法,以使 GC 的工作更容易一些。

          class Test {
          
          protected void finalize() throws Throwable {
              System.out.println("test finalized");
          };
          
          public static void main(String[] args) {
              Thread t = new MyThread();
              t.start();
              Test test = new Test();
          
              try {
                  System.out.println(t.getState());
                  // t.join();
                  t = null;
                  test = null;
                  // System.out.println(t.getState());
          
                  Thread.sleep(500); // change this to say 2 or 3 sec i.e, until run() completes. The thread object will be finalized
                  System.gc();
                  System.out.println("gc call done..");
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
          
          }
          

          }

          class MyThread extends Thread {
          
          protected void finalize() throws Throwable {
              System.out.println("MyThread instance finalized..");
          };
          
          @Override
          public void run() {
              for (int i = 0; i < 5; i++) {
                  try {
                      Thread.sleep(300);
                  } catch (InterruptedException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
          
                  System.out.println(i);
          
              }
          }
          

          }

          O/P: case : 1 --> 如果调用 gc 时线程正在运行

          RUNNABLE
          0
          gc call done..
          test finalized
          1
          2
          3
          4
          

          case :2 --> IF 线程在调用 gc 时完成运行

                RUNNABLE
              0
              gc call done..
              test finalized
              1
              2
              3
              4
          MyThread instance finalized..
          

          符合垃圾回收条件并不一定意味着该对象将从内存中删除。垃圾收集时由底层操作系统/JVM自行决定。但是如何确保(通过 Java 程序或外部工具)对象已完全从内存中删除?

          在Java中,你只能说当object变成unreachable(不是GCed,unreachable)。 finalize() 将在您的 onjecy 变得无法访问时被调用。

          如果一个线程在完成其 run() 方法后就被称为死了,为什么我仍然能够在同一个线程对象上执行 isAlive() 或 getState()?这两个调用分别返回 false 和 RUNNABLE。 - 线程可能已完成执行。但它仍然会处于一个状态。 终止状态。您正在线程实例上调用这些方法。所以,它仍然保存着一些状态数据。

          【讨论】:

          • 实际上可以辨别出两种状态:finalizablefinalized。这可以从幻像引用队列中获得。然而,即使finalized也不意味着“从内存中删除”。
          • @MarkoTopolnik - 同意。 finalized 仅表示实例现在无法访问
          • finalizable 也意味着“无法到达”,但并非绝对如此,因为可能会复活。所以是的,实际上只有“最终确定”意味着“绝对无法访问”。
          • @MarkoTopolnik - 但即使是 finalized 实例也可以复活,对吧?
          • 否,finalized 表示终结器方法已运行并且对象仍然无法访问。这意味着它绝对可以作为垃圾收集,而此时运行时已经失去了对它的跟踪;您只需通过ReferenceQueue 获得关于该事件的事件
          【解决方案6】:

          我相信垃圾收集器会在内存中没有对象的引用时收集对象。在线程的情况下,进程正在运行,因此垃圾收集器不会收集线程对象。

          每个对象树都必须有一个或多个根对象。只要应用程序可以到达这些根,整个树就可以通过

          javabook.compuware.com/content/memory/how-garbage-collection-works.aspx
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-07-18
            • 2014-03-04
            • 2022-01-14
            • 1970-01-01
            • 2016-08-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多