JVM 何时将成员变量引用存储在堆栈上?

我正在阅读Java SE 规范的第 12.6.1 节,它说:


可以设计优化程序的转换,将可到达的对象的数量减少到比那些天真地认为是可到达的要少。例如,Java 编译器或代码生成器可能会选择将不再使用的变量或参数设置为 null,以使此类对象的存储空间可能更快地被回收。


如果对象字段中的值存储在寄存器中,则会出现另一个示例。然后程序可能会访问寄存器而不是对象,并且永远不会再次访问对象。这意味着该对象是垃圾。请注意,仅当引用在堆栈上而不是存储在堆中时才允许进行这种优化。


相关代码为:


class Foo {

    private final Object finalizerGuardian = new Object() {

        protected void finalize() throws Throwable {

            /* finalize outer Foo object */

        }

    }

我的问题是哪种 JVM 会将 finalizerGuardian 存储在堆栈而不是堆中,为什么?


猛跑小猪
浏览 99回答 2
2回答

湖上湖

代码示例用于说明引用文本的最后一句话,“请注意,仅当引用在堆栈上而不是存储在堆中时才允许进行这种优化”,您将其从解释中扯下来有点奇怪文本:例如,考虑Finalizer Guardian模式: class Foo {     private final Object finalizerGuardian = new Object() {         protected void finalize() throws Throwable {             /* finalize outer Foo object */         }     } } super.finalize如果子类覆盖并且finalize未显式调用super.finalize.如果允许对存储在堆上的引用进行这些优化,那么 Java 编译器可以检测到该finalizerGuardian字段从未被读取,将其清空,立即收集对象,并尽早调用终结器。这与意图背道而驰:程序员可能想在 Foo 实例变得无法访问时调用 Foo 终结器。因此,这种转换是不合法的:只要外部类对象可访问,内部类对象就应该是可访问的。所以代码示例说明了一个限制。规范中提到的“优化转换”包括在逃逸分析证明对象是纯本地的之后应用的对象标量化,换句话说,优化下的代码跨越对象的整个生命周期。但它不需要这样的本地对象。正如规范已经提到的,优化的代码可以将对象的字段保留在 CPU 寄存器中,而无需重新读取它们,因此,不再需要保留对象引用。同样,仍然在范围内的引用变量可能未被使用。如果该引用是对某个对象的唯一引用,则将其从优化代码中删除允许更早地进行垃圾回收。这两种情况仍然允许Foo实例被更早地消除或收集。这反过来将允许较早地收集 . 引用的对象(不再是)finalizerGuardian。但这并不能抵消这种限制的意图。规范将优化限制为不允许内部对象早于外部对象被收集,但将两者收集在一起没有问题,包括早于天真的预期。通常,任意大的对象图可能会在单个垃圾收集周期中被收集,可能比天真预期的要早,甚至完全被优化掉。

largeQ

这种优化(逃逸分析)的一个经典例子是一个带有Point类的计算:class Point {&nbsp; &nbsp; double x;&nbsp; &nbsp; double y;&nbsp; &nbsp; public Point(final double x, final double y) {&nbsp; &nbsp; &nbsp; &nbsp; this.x = x;&nbsp; &nbsp; &nbsp; &nbsp; this.y = y;&nbsp; &nbsp; }&nbsp; &nbsp; double length() {&nbsp; &nbsp; &nbsp; &nbsp; return Math.sqrt(x * x + y * y);&nbsp; &nbsp; }&nbsp; &nbsp; static double calc() {&nbsp; &nbsp; &nbsp; &nbsp; double result = 0;&nbsp; &nbsp; &nbsp; &nbsp; for (int i = 0; i < 100; i++) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // this allocation will be optimized&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Point point = new Point(i, i);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result += point.length();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; return result;&nbsp; &nbsp; }}内联后就new不需要了,因为我们可以将所有字段提取到局部变量中,例如Point point = new Point(i, i);double x = point.x;double y = point.y;result += Math.sqrt(x * x + y * y);->Point point = new Point(i, i);double x = i;double y = i;result += Math.sqrt(x * x + y * y);现在很明显这new Point(i, i)是没用的,JIT 只是删除了这一行。请注意,分配是在堆栈上,即在局部变量中。如果它在一个字段中,我们将无法进行优化,因为它存储在堆中。它是如何工作的。关于您的代码被剪断:finalizerGuardian将始终在字段中(存储在堆中),而 JVM 对此分配无能为力。此外,如果Point上面示例中的类包含此类字段,我认为转义分析无法删除分配,因为它可能会改变原始行为。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java