【问题标题】:Memory allocation with Thread使用线程分配内存
【发布时间】:2012-05-11 15:44:44
【问题描述】:

我想知道如果你在一个方法中声明一个本地线程会发生什么?通常,一旦函数返回,所有局部变量都会消失,因为它们都分配在堆栈上。但是,似乎本地线程将是一个不同的故事。是对的吗?

public int A() {
    Thread t = new Thread() {
        doSomething();
    }
    t.start();
    return -1;
 }

【问题讨论】:

  • 这个问题很难阅读/理解。您可以对其进行编辑以使其充实吗?更好地解释你在问什么。显示一些简洁。代码示例?

标签: java multithreading memory-management


【解决方案1】:

线程是它自己的 GC 根。因此,无论何时创建一个线程,不管它的创建上下文如何,它都不会准备好进行 GC,直到它的 run 方法完成。即使本地方法完成并且线程仍然处于活动状态也是如此。

例子:

public void doSomeAsync(){
   Thread th = new Thread(new Runnable(){
      public void run(){
          Thread.sleep(500);
      }
   });
   th.start();
   //do something else quickly
}

//do somethign else quickly 之后,任何未转义该方法的定义都会被标记为 GC。线程 th 不会被标记为 GC,而是正确放置在具有自己的线程堆栈的堆上。

【讨论】:

  • +1 很好的回答约翰。你能展示一些示例代码来详细说明吗?
  • 这是否意味着本地线程将被放置在堆上,即使它在方法中声明为本地?
  • @user1389813 Java 将进行转义分析以确定对象是否可以本地放置在堆栈或堆上。一个线程本质上会逃逸,因此将被放置在堆上。你可以在这里阅读逃逸分析ibm.com/developerworks/java/library/j-jtp09275/index.html
【解决方案2】:

约翰的回答很好,但我想我会添加更多细节。这是一个代码示例,我将使用它来显示特定的变量用法。

 public void startThread() {
      long var1 = 10;
      byte[] var2 = new byte[1024];
      final byte[] var3 = new byte[1024];
      final byte[] var4 = new byte[1024];
      Thread thread = new Thread(new Runnable() {
          private long var5 = 10;
          private byte[] var6 = new byte[1024];
          public void run() {
              int var7 = 100;
              byte[] var8 = new byte[1024];
              System.out.println("Size of var4 is " + var4.length);
              baz();
              ...
          }
          private void baz() {
              long var9 = 2;
              byte[] var10 = new byte[1024];
              ...
          }
      });
      thread.start();
 }

所以我们在这里围绕线程分配了许多变量。我们还拥有Thread 对象本身以及线程正在运行的Runnable 目标。

  • thread -- 尽管它看起来是startThread() 的本地线程,但关联的Thread 也由JVM 管理。只有在run() 方法完成并且Thread 被JVM 收割之后才会进行GC。 Thread 被 GC 后,Thread 使用的所有字段都可以被 GC。
  • Runnable -- 这个匿名类是线程正在运行的。它可以在 Thread 完成并被 GC 后进行 GC。
  • var1 -- 这是startThread() 的本地变量,并在堆栈上分配。当startThread() 方法完成并重用堆栈时,它将被覆盖。
  • var2 -- 这是startThread() 的本地变量,并在堆上分配。它不能被线程使用,因为它不是final。可以在 startThread() 完成后进行 GC。
  • var3 -- 这是startThread() 的本地变量,并在堆上分配。这是final,所以它可以被线程使用,但它不是。可以在startThread() 完成后进行 GC。
  • var4 -- 这是startThread() 的本地变量,并在堆上分配。这是final,由线程使用。只有在 startThread() 方法完成 并且 RunnableThread 都被 GC 后,它才能被 GC。
  • var5 -- 这是Runnable 内的本地字段,并作为Runnable 匿名类的一部分在堆上分配。它可以在 Runnable 完成并且 RunnableThread 被 GC 后进行 GC。
  • var6 -- 这是Runnable 内部的一个本地字段,并在堆上分配。它可以在 Runnable 完成并且 RunnableThread 被 GC 后进行 GC。
  • var7 -- 这是run() 方法内部的一个本地字段,分配在新线程的堆栈上。当run() 方法完成并重用堆栈时,它将被覆盖。
  • var8 -- 这是run() 方法内部的一个本地字段,并在堆上分配。在run() 方法完成后,它可以被 GC。
  • var9 -- 这是baz() 方法内部的一个本地字段,分配在新线程的堆栈上。当baz() 方法完成并重用堆栈时,它将被覆盖。
  • var10 -- 这是baz() 方法内部的一个本地字段,并在堆上分配。在 baz() 方法完成后,它可以被 GC。

其他注意事项:

  • 如果新线程从未启动,则可以在startThread() 完成后对其进行 GC。 Runnable 以及与之关联的所有变量也可以被 GC 处理。
  • 如果您在startThread() 中声明了final long varX 原语并在线程中使用,那么它必须分配在堆上而不是 堆栈上。当startThread() 完成后,它仍然会被使用。

【讨论】:

  • var 9 将分配在该匿名线程的堆栈上,对吗?因为每个线程都有自己的堆栈,但共享同一个堆。另外我认为,为了让内部类(匿名类)访问外部作用域上的变量,唯一的方法是将变量声明为 final,而不仅仅是因为对于 Thread,对吗?
  • 是的,var9 将在新线程的堆栈上。 var7 也一样。我已经编辑了我的答案以使其更清楚@user1389813。
  • @user1389813 如果新线程从未启动,则可以在 startThread() 完成后对其进行 GC。与之相关的所有变量也可以被 GC 处理。
【解决方案3】:

如果一个线程是从本地上下文启动的,该线程将继续执行,直到它的 Runnable 的 run 方法完成执行。

【讨论】:

    【解决方案4】:

    如果变量是原始变量,那么它将在堆栈上,并且在方法返回时将消失——但您线程的 Runnable 实例(或任何包含线程内容的实例)将有一个副本那个原始值。

    如果变量是引用类型,那么对象会在堆上分配并一直存在,直到不再有对它的引用,此时它有资格进行垃圾回收。该对象的 reference 在堆栈上,当方法返回时将消失,但与原语一样,线程的 Runnable 将拥有相同引用的副本(因此将保留该对象活着)。

    【讨论】:

    • 在方法内部声明的原始变量将在堆栈中。作为类字段的原始变量将在堆上。
    • @Gray 是的,我应该指定非字段变量。我倾向于将类/实例变量称为“字段”以减少这种歧义,虽然我认为这并不少见,但我同意严格来说它不是 JLS。
    【解决方案5】:

    如果您在方法中生成本地 Thread,则只有声明为 final 的本地方法变量会一直存在,直到 Thread 完成。当Thread 完成它的run() 方法时,线程和它在创建它的方法中可用的任何最终变量都会像其他所有东西一样被垃圾收集。

    澄清

    只有在原始方法中使用的final 变量和衍生线程的run() 方法将避免被垃圾收集,直到方法和run() 方法都完成。如果线程不访问变量,那么线程的存在不会阻止变量在原始方法完成后被垃圾回收。

    参考文献

    http://java.sun.com/docs/books/performance/1st_edition/html/JPAppGC.fm.html

    【讨论】:

    • Final 与字段是否“坚持”无关。 Final 只影响该字段是否可以重新分配,它还影响构造函数分配顺序。
    • 当然可以。如果我有一个在方法顶部声明最终变量的方法,然后生成一个在其 run() 方法中访问该最终变量的线程,只要线程仍在运行,该最终变量就不会被垃圾收集,因为该最终变量仍然可以通过线程在 JVM 的对象图中访问。无论生成线程的原始方法是否在 run() 方法之前返回,这都是正确的。
    • 哦,我明白了。我以为您在谈论线程的最终字段。请编辑您的答案以澄清。另外,除非线程实际访问最终字段,否则我不确定您是否正确 Ben。我已经通过测试验证了这一点。
    • 我很确定(虽然我现在懒得检查 JLS)并不是方法变量永远存在,而是它被复制到Runnable。 (在 Java 8 中,您甚至不需要将方法变量声明为 final 即可工作——它们不变就足够了,也就是说,如果您愿意,您可以将它们标记为 final。)跨度>
    • 我认为这可能取决于 JVM 实现,但 Java 规定的规定行为将是其他对象的外部访问将决定这些最终变量停留多长时间。
    猜你喜欢
    • 2021-01-23
    • 2010-10-21
    • 2018-10-21
    • 2023-03-13
    • 2016-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多