未初始化的原始实例变量会使用内存吗?

43
在Java中,如果没有初始化类级实例变量,声明该变量是否会占用内存?例如:int i;如果我不使用i = 5;进行初始化,那么它是否会使用任何内存?
细节:我有一个非常庞大的超类,许多不同的子类(不足以拥有自己的超类)是扩展的。某些子类不使用超类声明的每个原始类型。我是否可以将这些未初始化的基元保留为仅在必要的子类中初始化以节省内存?

2
关于你最后一段的内容,可以看看结构型设计模式,也许有些东西能帮到你。根据你的使用情况,享元模式可能会引起你的兴趣。 - Ibrahim Arief
4
在 Stack Overflow 成立六年并且已经有733987个Java问题之后,这为什么不是一个重复的问题呢? - Peter Mortensen
虽然这个问题已经在另一个问题中得到了回答,但我还是有些犹豫要不要关闭它,因为它非常相关:https://dev59.com/53E95IYBdhLWcg3wKqok - Jeroen Vannevel
3
当许多派生类未使用成员时,巨大的超类是一种设计上的不良迹象。您可能需要考虑使用组合而不是继承。 - Raedwald
1
当你在Java中似乎需要多重继承时,委托通常可以用来实现类似的结果,尽管不像继承那样自然/优雅。 - JimmyB
显示剩余4条评论
6个回答

72

您定义的类中所有成员都有默认值,即使您没有显式初始化它们,因此它们仍然会使用内存。

例如,每个 int 默认将被初始化为 0,并占用 4 个字节。

对于类成员:

int i;

等同于:

int i = 0;

这是JLS关于实例变量的说明:

如果类T有一个名为a的字段,它是一个实例变量,则在创建类T或任何T子类的新对象时,都会创建一个新的实例变量a并将其初始化为(§4.12.5)中的默认值。当包含该字段的对象不再被引用且完成了必要的对象finalization (§12.6)后,该实例变量将有效地停止存在。


2
@Eran 今天我在波兰维基百科上阅读关于“单例模式”的内容(针对波兰语用户 http://pl.wikipedia.org/wiki/Singleton_(wzorzec_projektowy)#Konsekwencje_stosowania)。根据这篇文章,使用单例模式的优点之一是,“由于延迟初始化,如果没有组件使用单例模式,则不会为单例模式的实例分配任何资源”。我知道单例模式不是原始类型,但它们是否具有一些默认值?它们在初始化之前是否真的不使用任何内存? - spoko
3
单例是只有一个实例的类。使用延迟初始化时,保存该实例的变量包含一个空引用。只有在类被实例化时,才会分配内存来存储这个实例。 - Eran
3
原文:@spoko 原始类型 != 类。原始类型默认分配内存,但对于引用类型则不是这样。引用类型的默认值为 null。翻译:@spoko 原始数据类型和类不同。原始数据类型默认会分配内存,但对于引用类型则不然。引用类型的默认值是 null - Suresh Atta
4
一个空引用的对象类型属性即使为null,也将占据32/64位的存储空间,因此本质上与int=0相同。而且引用本身不会占用更多的空间!只有额外分配给新对象的空间才会增加……但是如果谈论Java类加载器和所有这些内容,那么99%都是过早优化,对任何人都没有好处… - Falco
1
@Raedwald - 如果在不使用它的实例中消除某些实例中使用的字段,则JVM/JITC必须维护该类的两个代码版本,因为其他实例字段的偏移量将被缺失字段所替代。 - Hot Licks
显示剩余3条评论

17

是的,即使您没有分配任何值,内存仍然会分配。

int i;

它需要占用 32 位 的内存(分配),不管你是否使用它。

有些子类不会使用超类声明的每一个原始类型,我可以简单地将这样的原始类型保持未初始化,并且仅在必要的子类中进行初始化以节省内存吗?

同样地,无论您在何处初始化,都会分配内存。

唯一需要注意的是找到未使用的原始类型并将其删除。

编辑:添加一个更重要的点,即与原始类型不同,默认情况下引用值为null,它占用了内存。

 4 bytes(32-bit) 
 8 bytes on (64-bit)

1
我无法移除它们。其他子类使用它们。扩展多个超类可以解决我的问题,但在Java 7中不可能实现。 - WVrock
1
@WVrock 好的,就按原样保留它们。 - Suresh Atta
1
@WVrock 或者在任何其他版本的Java中。 - Dave Newton
1
@DaveNewton 我听说在Java 8中可以使用接口来实现多重继承。但这仍然不算是继承多个超类。 - WVrock
1
@WVrock 实现多个接口一直以来都是可能的;Java 8 只是增加了默认方法实现。 - Dave Newton
@DaveNewton 在Java 7及以下版本中,多个实现是可能的,但与超类相比,接口非常有限。它们太有限了,不能称之为多重继承。它们所能做的只是添加方法名称和最终变量。如果你问我,它们甚至不能与超类一样有用。我不确定在Java 8中是否可以称之为多重继承。例如,在这里,即使在Java 8中,它们也无法解决我的问题。 - WVrock

11

原始问题讨论了类级别变量,答案是它们确实使用空间,但查看方法范围的变量也很有趣。

我们来看一个小例子:

public class MemTest {
    public void doSomething() {
        long i = 0;  // Line 3
        if(System.currentTimeMillis() > 0) {
            i = System.currentTimeMillis();
            System.out.println(i);
        }
        System.out.println(i);
    }
}

如果我们查看生成的字节码:

  L0
    LINENUMBER 3 L0
    LCONST_0
    LSTORE 1

好的,正如预期的那样,我们在代码的第3行分配一个值,现在如果我们将第3行更改为(由于编译器错误而删除第二个println):

long i; // Line 3

如果我们仅仅是声明一个未赋值的方法变量并检查字节码,那么第3行不会生成任何内容。因此,在这一点上不使用任何内存。实际上,只有在第5行赋值给该变量时才会发生 LSTORE 操作。因此,声明未赋值的方法变量不会使用任何内存,并且实际上不会生成任何字节码。它相当于在首次分配变量时进行声明。


这是关于其他事情的大量铺垫材料。如果没有别的,先回答问题可能会更好,然后再添加额外信息。 - Dave Newton
2
没有生成任何东西的原因是编译器足够智能,可以将其优化掉。否则,未初始化(且未使用)的局部变量会占用内存。 - async
自从这个答案发布以来,已经有一个关于方法作用域的后续问题发布了https://dev59.com/IF8d5IYBdhLWcg3wmDOf - Stephen Kennedy
@user16547,我认为这不正确——在Java中,你不能有一个分配了内存但未初始化的原始类型,因为内存位置必须包含一些数据。由于Java规范指出本地变量未初始化,这也意味着它们也没有分配内存。 - BarrySW19
@BarrySW19 当创建该特定方法的堆栈帧时,它会为局部变量分配足够的内存。如果它不能聪明地意识到某个变量未被使用且完全无用,那么它将在堆栈帧中天真地为其分配足够的内存。 - async

6

是的。即使您不初始化它们,类级变量也会分配其默认值。

在这种情况下,您的int变量将被赋值为0,每个变量占用4字节


4
由于这是一个实现细节,因此Java语言规范和Java虚拟机规范都没有指定答案。事实上,JVMS §2.7明确表示:

对象的表示

Java虚拟机不强制要求对象具有任何特定的内部结构。

理论上,符合规范的虚拟机可以使用一组位标志来实现具有许多字段的对象,以标记已设置为非默认值的字段。最初不会分配任何字段,标志位将全部为0,对象将很小。当第一次设置字段时,相应的标志位将被设置为1,并且对象将被调整大小以为其腾出空间。[垃圾收集器已经提供了必要的机制,以暂停运行中的代码,以便在堆中重新定位活动对象,这对于调整其大小是必要的。]

实际上,这并不是一个好主意,因为即使它节省了内存,它也很复杂和缓慢。访问字段需要临时锁定对象以防止多线程导致的损坏;然后读取当前标志位;如果字段存在,则计算所需字段相对于对象基址的当前偏移量来计算;然后读取字段;最后解锁对象。
因此,没有通用的Java虚拟机会这样做。一些具有过多字段的对象可能会从中受益,但即使是它们也不能依赖于它,因为它们可能需要在常见的虚拟机上运行,而这些虚拟机不会这样做。
当对象首次实例化时分配所有字段空间的平面布局是简单且快速的,因此这是标准的。程序员假设对象是按照这种方式分配的,并因此设计他们的程序以最大限度地利用它。同样,虚拟机设计师优化以使该使用快速。
最终,字段的平面布局是一种约定,而不是规则,尽管您仍然可以依靠它。

2

在Java中,当您声明类属性(例如String str;)时,您声明了一个对象的引用,但除非将值赋给它str=value;,否则它尚未指向任何对象。但是,正如您可能猜到的那样,即使没有指向内存位置,引用本身也会消耗一些内存。


5
但这些不是参考文献;原帖的问题特别是关于基本类型的。 - Dave Newton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接