java - 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 ]
解决方案
为了证明一个字段是完全未使用的,即不仅现在未使用而且将来也未使用,仅使用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 中,或者被完全消除。
并不是说在这种情况下,您不必担心继承关系。由于这种优化只适用于跨越对象整个生命周期的代码路径,它包括它的分配,因此,它的确切类型是已知的。并且在对象上操作的所有方法都必须已经内联。
由于此时外部对象不再存在,因此消除未使用的内部对象也不会与上面引用的规范相矛盾。
因此,通常没有优化删除未使用的字段,但对于特定Foo
或Bar
实例,它可能会发生。对于这些情况,即使存在可能使用该字段的方法也不会造成问题,正如优化器此时所知道的那样,它们是否在对象的生命周期内实际被调用。
推荐阅读
- sql - Sql 查询聚合结果
- python - 如何删除新动画(如跳跃或死亡)的 blitted 图像
- angular - 无法检索我的 cardTiles var 中的值
- c++ - 如何使用 strlen() 获取数组的长度?
- php - Woocommerce 购物车数量更改停止更改最终价格
- sails.js - 如何处理sails.js 助手中的非显式错误?
- php - 如果用户在 WooCommerce 中购买了特定产品,则更改“添加到购物车”文本
- scala - 导入 org.apache.spark.SparkConf 在 Spark-shell 中不起作用
- python - 一次将带有分隔符的多列拆分为python中的多列
- typescript - InversifyJS 绑定装饰器和惰性注入