最终变量是不是在每个实例的基础上占用内存?

Posted

技术标签:

【中文标题】最终变量是不是在每个实例的基础上占用内存?【英文标题】:Does final variables occupy memory on a per-instance basis?最终变量是否在每个实例的基础上占用内存? 【发布时间】:2019-03-09 19:01:55 【问题描述】:

我已阅读有关最终实例变量的其他问答,并且我开始知道非静态最终实例变量是在堆中为类的每个实例创建的但在 java -herbert schildt 的完整参考文献中 据说:

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

哪个是正确的?

【问题讨论】:

最终实例变量确实会占用内存。每个实例都可能不同,并且该值必须存储在某个地方。报价中的信息有误。 谢谢!书的台词真的错了吗?如果有的话,还有其他方法可以证明它是正确的吗?只是好奇! The book 似乎假设最终变量必须是编译时常量。这似乎是一个错误。它还说“这意味着您必须在声明最终变量时对其进行初始化。”这也不一定是真的,特别是对于可以在构造函数中分配的最终实例变量。 如果最终变量在它们的声明中被初始化并且只为它分配一次内存,那么编译器是否可以做出这个假设,因为显然每个创建的实例对于最终变量都有相同的值。是编译器足够聪明来做这种优化?以及在某些版本的java中是否允许? 【参考方案1】:

第一个陈述是正确的......虽然仅限于 final 实例字段。

第二个说法不正确。

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

如果变量是局部变量或方法的形参变量,则内存单元将在堆栈上。

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

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

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

(实际上,您甚至可以反射性地修改final 字段。当您这样做时,JVM 规范并未指定该行为,并且在某些情况下可能会相当令人惊讶。)


final 变量是否在每个实例的基础上占用内存?

不,因为:

静态变量不会“在每个实例上占用内存”,无论它们是否为 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 定义)之外的所有情况。

理论上,JIT 编译器可能会看到 Test.test 只使用相同的常量值调用,并将该值内联到本机代码中,这样就不需要内存单元。但是,这种优化对性能的影响很小,并且不太可能适用于实际代码。所以我怀疑它是否值得实施它。

(由于将test 的主体内联到其调用站点,val 更有可能被优化掉。这是一个更一般的优化,绝对值得。这可能允许窥视孔优化器推断内联代码中不需要val。)

【讨论】:

以上是关于最终变量是不是在每个实例的基础上占用内存?的主要内容,如果未能解决你的问题,请参考以下文章

vmstat-虚拟内存查看实例

python基础之面向对象进阶

Java基础--static关键字

Objective-C 中的实例变量是不是默认设置为 nil?

sizeof()计算结构体的大小

Java中的类变量实例变量类方法实例方法的区别