【问题标题】:Do uninitialized primitive instance variables use memory?未初始化的原始实例变量是否使用内存?
【发布时间】:2014-10-27 10:13:04
【问题描述】:

在 Java 中,声明一个类级别的实例变量而不初始化它会消耗内存吗?
例如:如果我不使用i = 5; 初始化它,int i; 是否会使用任何内存?

详情:

我有一个巨大的超类,它扩展了许多不同的(差异不足以拥有自己的超类)子类。一些子类不使用超类声明的每一个原语。我可以简单地将这些原语保留为未初始化,并且只在必要的子类中初始化它们以节省内存吗?

【问题讨论】:

  • 关于你的最后一段,看看structural design patterns,也许有什么可以帮助你的。根据您的用例,flyweight pattern 可能会引起您的兴趣。
  • 享元模式是共享数据以避免为每次使用创造相同的价值。我要做的是避免创建不会使用的值。感谢您的链接。我会研究其他模式。
  • 在 Stack Overflow 推出 6 年多之后,在 733987 个 Java 问题之后,这怎么不是重复的?
  • 一个巨大的超类是一种设计味道,特别是如果许多派生类不使用成员。您可能需要考虑使用组合而不是继承。
  • 当您似乎需要在 Java 中进行多重继承时,delegation 通常可以用来实现类似的结果,尽管不像继承那么自然/优雅。

标签: java memory-management initialization primitive


【解决方案1】:

类中定义的所有成员都有默认值,即使您没有显式初始化它们,所以它们确实使用内存。

例如每个int都会默认初始化为0,会占用4字节。

对于班级成员:

int i;

等同于:

int i = 0;

JLS 对实例变量的看法如下:

如果类 T 有一个作为实例变量的字段 a,则创建一个新的实例变量 a 并将其初始化为 默认值 (§4.12.5) 作为每个新创建的类对象的一部分T 或作为 T 子类的任何类(第 8.1.4 节)。在对象的任何必要终结(第 12.6 节)完成后,当不再引用作为字段的对象时,实例变量实际上不再存在。

【讨论】:

  • @Eran 今天我正在阅读波兰语维基百科上的“Singleton”(波兰语用户pl.wikipedia.org/wiki/…)。根据这篇文章,使用单例的优点之一是“由于延迟初始化,如果没有使用单例的组件,则不会为单例的实例提供任何资源”。我知道单例不是原始类型,但它们没有一些默认值吗?是真的吗,它们在初始化之前不使用任何内存?
  • @spoko Singleton 是具有单个实例的类。当使用延迟初始化时,持有该实例的变量包含一个空引用。只有当类被实例化时,才会为实例分配内存。
  • @spoko Primitives != 类。 Primitives 默认分配内存。但不作为参考。它们具有默认值null
  • @sᴜʀᴇsʜᴀᴛᴛᴀ 空引用 Object-Type 属性将占据其 32/64 位的份额,即使它为空,所以它本质上与 int=0 相同。并且参考永远不会占用更多空间!只会为新对象添加额外分配的空间......但是用 java 类加载器谈论这个,一切都处于 99% 的过早优化中,不会有任何好处......
  • @Raedwald - 如果在某些实例中使用的字段在不使用它的实例中被删除,则 JVM/JITC 必须为该类维护两个版本的代码,因为其他实例字段的偏移量将被缺少的字段取代。
【解决方案2】:

是的,虽然您没有为它分配任何值,但内存仍在分配。

int i;

这需要32 bit 内存(分配)。不管你用不用。

一些子类不使用超类声明的每一个原语。我可以简单地将这些原语保持为未初始化,并且只在必要的子类中初始化它们以节省内存吗?

同样,无论您在哪里初始化,内存都会分配。

您唯一需要注意的是,只需找到未使用的原语并将其删除。

编辑: 与默认值的原始引用不同的一点是null,它带有一个内存

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

【讨论】:

  • 我无法删除它们。其他子类使用它们。扩展多个超类可以解决我的问题,但在 java 7 中这是不可能的。
  • @WVrock 好的。让它们保持原样。
  • @WVrock 或任何其他 Java 版本。
  • @DaveNewton 听说java 8可以使用接口来实现多重继承,但仍然不算扩展多个超类。
  • @WVrock 实现多个接口基本上永远是可能的; Java 8 仅添加了默认方法实现。
【解决方案3】:

最初的问题是关于类级别的变量,答案是它们确实使用空间,但看看方法范围的变量也很有趣。

我们举个小例子:

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 行生成任何内容。因此,答案是此时没有使用内存。事实上,LSTORE 仅在我们分配给变量时出现在第 5 行。因此,声明一个未分配的方法变量不会使用任何内存,实际上也不会生成任何字节码。这相当于在您首先分配给它的地方进行声明。

【讨论】:

  • 这是很多关于其他事情的引导材料。如果没有其他问题,最好先回答问题,然后添加其他信息。
  • 什么都没有生成的原因是编译器足够聪明,可以优化它。否则,未初始化(和未使用)的局部变量会占用内存。
  • 自发布此答案以来,已发布有关方法范围stackoverflow.com/q/26632799/397817的后续问题
  • @user16547 我不认为这是正确的 - 你不能有一个分配内存但在 Java 中未初始化的原语,因为内存位置必须包含一些数据。正如 Java 规范所说,局部变量未初始化,这意味着它们也未分配内存。
  • @BarrySW19 当为该特定方法创建堆栈帧时,它会为局部变量分配足够的内存。如果它不够聪明地意识到变量未被使用且完全无用,它会天真地在堆栈帧中为它分配足够的内存。
【解决方案4】:

是的。在您的类级别变量中,即使您没有初始化它们,它们也会分配给它的默认值。

在这种情况下,您的 int 变量将分配给 0,并且每个变量将占用 4 bytes

【讨论】:

    【解决方案5】:

    Java 语言规范和 Java 虚拟机规范都没有指定答案,因为它是一个实现细节。其实JVMS §2.7 specifically says

    对象的表示

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

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

    在实践中,这不是一个好主意,因为即使它节省了内存,它也是复杂且缓慢的。访问字段需要临时锁定对象以防止由于多线程造成的损坏;然后读取当前标志位;如果该字段存在,则计算设置位以计算所需字段相对于对象底部的当前偏移量;然后读取字段;最后解锁对象。

    所以,没有通用 Java 虚拟机可以做这样的事情。一些具有大量字段的对象可能会从中受益,但即使它们也不能依赖它,因为它们可能需要在不这样做的通用虚拟机上运行。

    在第一次实例化对象时为所有字段分配空间的平面布局既简单又快速,因此这是标准。程序员假定对象是以这种方式分配的,因此相应地设计他们的程序以最好地利用它。同样,虚拟机设计人员会进行优化以加快使用速度。

    最终,字段的平面布局是一种约定,而不是规则,尽管您无论如何都可以依赖它。

    【讨论】:

      【解决方案6】:

      在 Java 中,当您声明一个类属性(例如 String str;)时,您声明的是对一个对象的引用,但它还没有指向任何对象,除非您影响它的值 str=value;。但正如您可能猜到的那样,即使没有指向内存位置,引用也会消耗自己的一些内存。

      【讨论】:

      • 但这些不是参考资料; OP 专门询问原语。
      猜你喜欢
      • 1970-01-01
      • 2013-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多