【发布时间】:2019-09-15 21:10:35
【问题描述】:
我怀疑方法局部变量仅在方法执行时才存在。另外,当Eden或Long-generation块溢出(次要/主要GC)等时触发GC......那么,如果在方法体的末尾Eden没有溢出,所以没有必要触发GC。尽管没有触发主要/次要 GC,我们将销毁所有局部变量。是怎么做到的?
【问题讨论】:
我怀疑方法局部变量仅在方法执行时才存在。另外,当Eden或Long-generation块溢出(次要/主要GC)等时触发GC......那么,如果在方法体的末尾Eden没有溢出,所以没有必要触发GC。尽管没有触发主要/次要 GC,我们将销毁所有局部变量。是怎么做到的?
【问题讨论】:
方法局部变量(或通常称为“局部变量”)分配在每个线程的堆栈上。变量本身不受垃圾收集的影响。当方法调用(正常或异常)终止时会自动回收它们1。
对象是另一回事。对象(包括数组)通常2 分配在堆上,它们会受到垃圾回收。
那么通过方法分配并分配给局部变量的对象(或数组)呢?
首先,局部变量持有对对象的引用。对象存储在堆中(见上文)。
当垃圾收集器运行时(您通常不知道什么时候会运行!),它将检查任何现有的局部变量以查找仍在进行中的方法调用。如果变量包含对对象的引用,则将这些对象添加到要保留的对象列表中……并检查它们是否引用其他对象,等等。
因此,总而言之,局部变量在方法调用结束时会自动销毁,但这些变量引用的对象将继续存在,直到 GC(最终)发现它们无法访问。
1 - 我们需要考虑从内部类或在变量范围内声明的 lambda 访问的局部变量。 lambda 实例的类可能会传递到某个地方,以便在方法返回后可以使用它。在这种情况下,您会认为局部变量需要在方法返回后存在。实际上,发生的情况是局部变量被复制到表示内部类或 lambda 实例的对象中的合成字段。然后类 / lambda 使用该字段中的值。原始变量确实在其方法终止时消失。
2 - 最近的 Hotspot JIT 编译器有一个称为“转义分析”的可选优化,用于查找可以在线程堆栈上分配由方法调用创建的对象的情况。默认情况下不启用此功能。如果一个对象是在栈上分配的,那么当方法调用结束时它会被回收。 GC不参与。
3 - 你说:“当 Eden 或 Long-generation 块溢出(次要/主要 GC)等时触发 GC...”。不一定如此。一些低暂停收集器在相应的空间填满之前被触发。但是,这不会改变上述任何内容。
【讨论】:
垃圾收集器——有时也被称为死神——按照自己的时间表运行,并收集超出参考的对象。 当然,局部变量在方法退出后不能被引用,因为它们超出了范围,所以对你的程序来说它们是死的*,但它们仍然存在于堆上,直到 GC 运行。
在正常情况下(和大多数异常情况),你不需要告诉死神什么时候做它的工作。它会在需要的时候悄无声息地来,带走那些不再需要的东西。这是使用高级语言工作的主要优势之一:可以安全地假设您永远不需要考虑管理死对象的释放等事情。你可以把它们扔在你的肩膀上,知道它们永远不会打扰你。我想有一些高性能、高需求的应用程序需要摆弄 GC,但这是一种优化,除非你有非常好的相反证据,否则应该总是认为它为时过早。
*当然,返回给调用函数的局部变量除外,它们可能成为该范围内的局部变量并获得更多的生命。一般来说,规则是:如果你的代码的某些部分仍然关心变量,它不会被垃圾回收,如果你的程序没有任何部分关心它,那么你不需要考虑它。
【讨论】:
Java 虚拟机使用线性堆栈来管理(存储/访问)局部变量和方法参数,因此它只是将堆栈指针设置为没有声明任何局部变量和参数并继续前进的点。不用GC了
【讨论】: