为什么Java的布尔原始类型大小没有定义?

120

Java虚拟机规范指出,布尔类型的支持是有限的。

Java虚拟机没有专门用于布尔值操作的指令。相反,对布尔类型进行操作的Java编程语言表达式被编译为使用Java虚拟机int数据类型的值。

上述内容暗示(尽管我可能理解有误),当操作布尔类型时会使用int数据类型,但这是一个32位的内存构造。考虑到布尔只表示1位信息:

  • 为什么不使用byte或short类型作为布尔的代理而选择了int呢?
  • 对于任何给定的JVM,确定存储布尔类型所需的确切内存量的最可靠方法是什么?
7个回答

126
简短回答:是的,布尔值被处理为32位实体,但布尔数组每个元素使用1字节。
较长回答:JVM使用32位堆栈单元,用于保存本地变量、方法参数和表达式值。小于1个单元的基元类型会填充,大于32位(long和double)的基元类型会占用2个单元。这种技术最大限度地减少了操作码的数量,但确实具有一些奇怪的副作用(例如需要屏蔽字节)。
存储在数组中的基元类型可能使用少于32位,并且存在不同的操作码来从数组中加载和存储基元类型的值。布尔值和字节值都使用baload和bastore操作码,这意味着布尔数组每个元素使用1字节。
就内存对象布局而言,这在“私有实现”规则中已经涵盖,它可以是1位、1字节,或者像另一个帖子所指出的那样,对齐到64位双字边界。最有可能的是,它采用底层硬件的基本字大小(32位或64位)。

就最小化布尔值使用的空间而言:对于大多数应用程序而言,这并不是一个问题。栈帧(保存本地变量和方法参数)并不是很大,在大范围内,对象中的离散布尔值也不是很大。如果您有许多带有许多布尔值的对象,则可以使用通过getter和setter管理的位字段。但是,您将支付一个在CPU时间上比在内存上罚款更大的惩罚。


对于布尔/字节类成员,它们也是4个字节吗?类实例整体分配在堆栈上,因此我可以想象JVM应该每个布尔/字节成员使用1个字节,并最终对完整的类实例进行4字节对齐。是这样吗?(如果您有证明这一点的参考资料,请分享) - dma_k
@dma_k:正如我在回答中所指出的,类实例的布局取决于具体实现。但是请注意,类实例不存储在堆栈中,它们存储在堆上(尽管您会看到一些关于JDK 7“逃逸分析”的引用,将对象从堆栈移动到堆上,但这似乎并非事实;请参见java.sun.com/javase/7/docs/technotes/guides/vm/…)。 - kdgregory
1
有时候,打包布尔值可能会更快。每当缓存大小很重要时,打包东西可能会更好。例如,分段素数筛法以32 kB(L1缓存大小)的块为单位工作,比非分段筛法快得多。在块之间存在一些开销,并且通过打包,您可以减少八倍的开销。我还没有测量过它。 - maaartinus
你在哪里找到布尔类型只能占用1个比特的信息? - Christoph S.
1
2023年,访问boolean[]byte[]的指令仍然与以前相同。但这并不意味着布尔数组必须使用每个元素一个字节的空间。使用相同的指令并不意味着指令必须执行相同的操作。就像无论字段的类型如何,只有一个getfield指令用于读取字段一样。无论是baload/bastore指令引用的是boolean[]还是byte[]数组,都是已知的。但由于规范禁止字撕裂,实现必须选择一个支持原子读写访问的本机元素类型。 - undefined
显示剩余2条评论

9
一个单一的布尔值在继承层次结构中的某个位置可能使用高达8个字节!这是由于填充造成的。更多细节可以在我的Java对象使用了多少内存?中找到:

回到一个布尔值占用多少的问题,是的它至少占用一个字节,但由于对齐规则,它可能会占用更多空间。我认为更有趣的是,一个布尔值数组将消耗每个条目一个字节,而不是一个比特,加上一些由于对齐和数组的大小字段而产生的开销。有些图算法需要大量的位字段,你需要知道,如果你使用布尔值数组,你需要几乎比实际需要的内存多8倍(1字节对1比特)。


你怎么使用 boolean[] 呢? - Thomas Jung
boolean[] 可以用作掩码。但有时候 BitSet 更好,因为它具有一些有用的方法。 - Michael Munsey

6
< p >《Java编程思想》第五版(O'Reilly)中说布尔原始类型为1字节。根据堆的检查结果,这可能是错误的。我想知道大多数JVM在为变量分配少于1个字节时是否存在问题。


3

中央处理器(CPU)的操作需要特定的数据类型长度。对于32位CPU,它们是32位长,因此在Java中称为'int'。如果长度低于或高于该长度,则必须将其填充或拆分为此长度,然后CPU才能处理它。这不需要花费太多时间,但如果您需要2个CPU周期而不是1个来执行基本操作,则意味着成本/时间加倍。

此规格专为32位CPU设计,以便它们可以使用其本机数据类型处理布尔值。

在速度和内存之间只能选择一个 - SUN选择了速度。


2

布尔映射是基于32位CPU设计的。int值有32位,因此可以在一次操作中处理。

以下是来自Peter Norvig的Java IAQ: Infrequently Answered Questions的解决方案,用于测量大小(略有不精确):

static Runtime runtime = Runtime.getRuntime();
...
long start, end;
Object obj;
runtime.gc();
start = runtime.freememory();
obj = new Object(); // Or whatever you want to look at
end =  runtime.freememory();
System.out.println("That took " + (start-end) + " bytes.");

1
由于这次对话涉及到原始类型,你需要有创意地进行测试,因为除非它们是实例或数组上的字段,否则原始类型不会存储在堆中。而且,这两种情况都无法回答Java将如何选择在堆栈中存储它的问题。 - Jesse

1
我们无法确定布尔数据类型的确切大小。它取决于虚拟机或不同操作系统之间的差异。

-11
为什么不像这样制作一个 .java 文件呢:

Empty.java

class Empty{
}

还有一个像这样的类:

NotEmpty.java

class NotEmpty{
   boolean b;
}

编译它们并使用十六进制编辑器比较 .class 文件。

7
这是完全不同的度量标准,与在内存中确定原始布尔类型大小无关。 - Joel

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