最终变量是否按实例占用内存?

时间:2018-10-04 11:42:29

标签: java class heap final instance-variables

我阅读了有关最终实例变量的其他问答,然后我知道为该类的每个实例在堆中创建了 非静态最终实例变量 但是在 java-herbert schildt的完整参考书中,据说:

  

声明为final的变量不会按实例占用内存。因此,final变量本质上是一个常量

哪个是正确的?

1 个答案:

答案 0 :(得分:3)

第一个语句是正确的……尽管限于实例的final字段。

第二个语句不正确。

一个final变量将在运行时某处占用内存。但是,该内存的位置取决于您声明为final的变量的类型。

  • 如果变量是局部变量或方法的形式参数变量,则存储单元将位于堆栈上。

  • 如果变量是非静态字段,则存储单元将是对象的一部分,并且将在堆中。

  • 如果变量是静态字段,则存储单元的位置将取决于实现。 (它可能在堆中,也可能在其他地方。)

应注意,某些static final字段是(但不是全部!)字段是编译常量,编译器有效地内联了它们的值。但是即使发生这种情况,在运行时仍然会有一个实际的字段占用存储单元,并且该存储单元的内容可以通过反射方式访问。

(实际上,您甚至可以反射性地修改final字段。执行此操作时,JVM规范未指定该行为,在某些情况下可能会令人惊讶。)


  

最终变量是否按实例占用内存?

不,因为:

  • 静态变量不会“在每个实例上占用内存”,无论它们是否为final
  • 局部变量和参数也可以是final,并且它们“不会在每个实例上占用内存”

  

如果final变量在其声明本身中初始化并且仅向其分配内存一次,则编译器可以做出此假设,因为显然创建的每个实例将对该最终变量具有相同的值。编译器足够聪明来执行这种优化吗?

考虑这个

public class Test
    public static test(final int val) {
        System.out.println(val);
    }
}

现在valfinal,但是它的值取决于您如何调用test方法。这在({javac)编译时是未知的,并且通常JIT编译器也不知道。确实,假设test是用不同的参数调用的,则无法直接优化val存储单元。

static final变量(符合JLS定义)外,所有情况均适用于Test.test变量。

从理论上讲,JIT编译器可能会看到val仅使用相同的常量值进行了调用,并将该值内联到本机代码中,因此不需要存储单元。但是,这种优化对性能的影响很小,并且不太可能应用于实际代码中。因此,我怀疑实现它是否值得。

test最有可能由于将val的主体内联到其呼叫站点而被优化了。这是更普遍的优化,肯定是值得的。这样可以允许窥孔优化器来推断内联代码中不需要class FooHolder { friend class BigClass; private: int foo_A; int foo_B; //... } class BarHolder { friend class BigClass; private: bool bar_A; bool bar_B; //... } class BigClass { public: int getFoo_A() { return m_FooHolder.foo_A; }; int getFoo_B() { return m_FooHolder.foo_B; }; //... bool getBar_A() { return m_BarHolder.bar_A; }; bool getBar_B() { return m_BarHolder.bar_B; }; //... private: FooHolder m_FooHolder; BarHolder m_BarHolder; }