首页 > 解决方案 > JVM会优化未使用的字段吗

问题描述

在优化我的代码时,我试图更多地了解 JVM,并且很好奇它是否(或者更具体地说是以何种方式)优化了未使用的字段?

我假设如果您在一个类中有一个从未被写入或读取的字段,那么当代码运行时,该字段将不存在于该类中。假设您有一个如下所示的课程:

public class Foo {
    public final int A;
    public final float B;
    private final long[] C = new long[512];
}

并且您只使用了变量 A 和 B,那么您可能会看到启动、维护和释放变量 C 对于本质上是垃圾数据的东西是多么浪费时间。首先,我假设JVM会发现这一点是正确的吗?

现在我的第二个也是更重要的例子是 JVM 是否在这里考虑继承?比如说 Foo 看起来更像这样:

public class Foo {
    public final int A;
    public final float B;
    private final long[] C = new long[512];

    public long get(int i) {
        return C[i];
    }
}

然后我假设这个类将存储在内存中的某个地方,有点像:

[答:4 | 乙:4 | C: 1024 ]

所以如果我有第二堂课看起来像这样:

public class Bar extends Foo {
    public final long D;
    
    @Override public long get(int i) {
        return i * D;
    }
}

然后突然这意味着字段 C 从未使用过,因此内存中的 Bar 实例将如下所示:

[答:4 | 乙:4 | C: 1024 | D: 8 ][ A: 4 | 乙:4 | D: 8 ]

标签: javaoptimizationmemoryjvmunused-variables

解决方案


为了证明一个字段是完全未使用的,即不仅现在未使用而且将来也未使用,仅使用private声明类是和未使用的是不够的。字段也可以通过反射或类似方式访问。基于此构建的框架甚至可能位于不同的模块中,例如,序列化是在java.base模块内部实现的。

此外,如果对象的垃圾收集是可观察的,例如对于具有非平凡finalize()方法或指向对象的弱引用的类,则适用额外的限制:

JLS §12.6.1.,实现终结

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

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

本节还给出了一个禁止此类优化的示例:

class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            /* finalize outer Foo object */
        }
    }
}

该规范强调,即使在其他方面完全未使用,内部对象也不能在外部对象变得无法访问之前完成。

这不适用于long[]没有终结器的数组,但它需要进行更多检查,同时降低了这种假设优化的多功能性。

由于 Java 的典型执行环境允许动态添加新代码,因此无法证明这种优化将保持不可观察。所以答案是,实际上没有这样的优化可以从类中消除未使用的字段。


然而,有一种特殊情况。当优化器正在查看的代码覆盖对象的整个生命周期时,JVM 可能会优化该类的特定用例。这由Escape Analysis检查。

当满足前提条件时,可以执行标量替换,这将消除堆分配并将字段转换为等效的局部变量。一旦您的对象被分解为三个变量A, B, ,C它们就会受到与局部变量相同的优化。因此,如果它们从未被读取或包含可预测的值,它们可能最终会出现在 CPU 寄存器中而不是 RAM 中,或者被完全消除。

并不是说在这种情况下,您不必担心继承关系。由于这种优化只适用于跨越对象整个生命周期的代码路径,它包括它的分配,因此,它的确切类型是已知的。并且在对象上操作的所有方法都必须已经内联。

由于此时外部对象不再存在,因此消除未使用的内部对象也不会与上面引用的规范相矛盾。

因此,通常没有优化删除未使用的字段,但对于特定FooBar实例,它可能会发生。对于这些情况,即使存在可能使用该字段的方法也不会造成问题,正如优化器此时所知道的那样,它们是否在对象的生命周期内实际被调用。


推荐阅读