内存分配与线程

3

我想知道如果在一个方法内声明一个本地线程会发生什么?通常,由于它们都是在堆栈上分配的,因此所有的局部变量在函数返回时都会消失。但是,似乎本地线程会有不同的情况。是这样吗?

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

这个问题很难读懂。你能否编辑一下,详细说明你的问题并更好地解释一下?可以展示一些简洁的代码示例吗? - Gray
5个回答

14

线程本身就是其自己的GC根。因此,无论何时创建一个线程,它都不会准备好进行GC,直到其运行方法完成。即使本地方法完成并且线程仍然存活,这也是正确的。

示例:

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

//快速执行其他操作之后,任何未逃逸出该方法的定义都会被标记为垃圾回收。线程th不会被标记为垃圾回收,并且正确地放置在堆上,具有自己的线程栈。


+1,好答案John。你能展示一些样例代码来详细说明吗? - Gray
这是否意味着即使在方法内声明为本地变量,本地线程也会被放置在堆上? - peter
Java会进行逃逸分析来确定一个对象是否可以被放置在栈上或堆上。线程本质上是逃逸的,因此会被放置在堆上。您可以在这里阅读有关逃逸分析的更多信息:http://www.ibm.com/developerworks/java/library/j-jtp09275/index.html - John Vint

7

John的回答很好,但我想补充一些细节。这是一个代码示例,我将使用它来展示特定变量的用法。

 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 -- 虽然看起来是局部变量,但与之关联的线程也由JVM管理。只有在run()方法完成并且线程被JVM收回后,该线程才会被垃圾回收。在线程被GC之后,所有由线程使用的字段都可以被GC。
  • Runnable -- 这个匿名类是线程运行的内容。在Thread完成并被GC后,它也可以被GC。
  • var1 -- 这是局部变量,分配在堆栈上。当startThread()方法结束并且堆栈被重用时,它将被覆盖。
  • var2 -- 这是局部变量,分配在堆上。由于它不是final,所以线程不能使用它。在startThread()完成后,它可以被GC。
  • var3 -- 这是局部变量,分配在堆上。它是final,因此线程可以使用它,但实际上并没有使用。在startThread()完成后,它可以被GC。
  • var4 -- 这是局部变量,分配在堆上。它是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()方法结束后它可以被垃圾回收。与之相关的Runnable和所有变量也会被回收。
  • 如果在线程中使用了在startThread()方法中声明的final long varX原始数据类型,那么它必须被分配到堆上而不是栈上。当startThread()方法结束后,它仍然处于使用状态。

变量9将被分配在匿名线程的堆栈上,对吗?因为每个线程都有自己的堆栈,但共享同一个堆。此外,我认为为了使内部类(匿名类)能够访问外部作用域中的变量,唯一的方法是将变量声明为final,而不仅仅是为了Thread,对吗? - peter
是的,var9将在新线程的堆栈上。var7也是如此。我已经编辑了我的答案,使其更清晰@user1389813。 - Gray
如果新线程从未启动,则可以在startThread()完成后将其GC。与之相关的所有变量也可以随之被GC。 - Gray

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

0
如果变量是原始类型,那么它将在堆栈上,并且在方法返回时将消失--但您的线程的Runnable实例(或包含线程主体的任何内容)将具有该原始值的副本。
如果变量是引用类型,则对象分配在堆上并一直存在,直到没有更多引用指向它,此时它就有资格进行垃圾回收。对该对象的引用在堆栈上,并且在方法返回时将消失,但与原始类型相同,线程的Runnable将具有该相同引用的副本(因此将保持该对象处于活动状态)。

一个在方法内声明的原始变量将位于堆栈中。作为类字段的原始变量将在堆上。 - Gray
@Gray 是的,我应该指定非字段变量。我倾向于将类/实例变量称为“字段”,以减少歧义,虽然我认为这并不罕见,但我同意它并不严格符合JLS。 - yshavit

-1
如果在方法内部生成一个本地的Thread,那么只有被声明为final的本地方法变量会一直存在,直到Thread完成。当Thread完成其run()方法时,线程和任何来自创建它的方法的可用的最终变量都将像其他所有内容一样被垃圾回收。 澄清 只有在原始方法和生成的线程的run()方法中使用的final变量才会在两个方法都完成之前不被垃圾回收。如果线程没有访问该变量,则线程的存在不会阻止在原始方法完成后对变量进行垃圾回收。 参考资料

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


1
Final与字段是否“保留”无关。Final只影响字段是否可以被重新分配,它还会影响构造函数分配顺序。 - Gray
1
当然可以。如果我有一个方法,在方法的顶部声明了一个final变量,然后生成一个线程,在其run()方法中访问该final变量,只要线程仍在运行,那么该final变量就不会被垃圾回收,因为该final变量仍然可以通过线程在JVM对象图中访问。这是无论生成线程的原始方法是否在run()方法之前返回都成立的。 - CodeBlind
哦,我明白了。我以为你在谈论线程内的最终字段。请编辑您的答案以澄清。此外,我不确定您是否正确,本,除非线程实际访问最终字段。我已经通过测试验证了这一点。 - Gray
我相当确定(虽然现在懒得去查JLS),方法变量并不会永久保存,而是被复制到Runnable的实例变量中。(在Java 8中,你甚至不需要将方法变量声明为final就可以工作-只要它们未更改即可,也就是说,如果你愿意,你可以将它们标记为final。) - yshavit
我认为这可能取决于JVM的实现,但Java规定的预定行为是,其他对象的外部访问将决定这些final变量存在的时间。 - CodeBlind
显示剩余3条评论

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