为什么Java布尔值必须至少为1个字节?

flo*_*w2k 4 java memory memory-management low-level

众所周知,C++ bool的大小必须至少为1个字节,以便为每个[/sf/answers/144519581/]创建指针.但是Java中没有指向原始类型的指针.然而,他们仍然至少占用1个字节[/sf/answers/26851821/].

为什么会这样 - 为什么Java不能boolean是1位大小?除了计算时间,如果有一个大boolean数组,肯定可以设想一个编译器进行适当的移位以检索对应于某个boolean值的单个位?

Bee*_*ope 7

没有理由说布尔值的大小必须是一个字节.实际上,在某些情况下,布尔值很可能不是1字节(有效)大小:当堆叠在堆栈上或对象中大于1个字节的其他元素旁边时,它们可能会更大(即添加)它们到一个对象可能会导致大小增长超过一个字节).

任何JVM都可以自由地实现1位布尔值,但据我们所知,没有人选择这样做,可能主要是因为:

  • 访问一位通常比访问一个字节更昂贵,特别是在写入时.

    要读取一点,使用"经典RISC"指令集的CPU通常需要一个额外的and指令来从布尔位的压缩字节(或更大字)中提取相关位.有些甚至可能需要额外的指令来加载常量and.在索引boolean, where the bit-index isn't fixed at compile-time, you'd need a variable shift. Some CPUs such as x86 have an easier time since they have memory source测试instructions, including specific bit-test instructions taking a variable position such asbt` 数组的情况下.这样的CPU在两种表示中可能具有相似的读取性能.

    写入更糟糕:boolean现在需要读取值,而不是简单的字节写入来设置值,修改相应的位并将其写回.一些平台,如x86的有记忆源地和目的地RMW指令,如andor,这将帮助,但这些仍显著比普通的写操作更加昂贵.在最坏的情况下,重复写入相同的元素将导致通过内存的依赖链,这可能会使代码减慢一个数量级(一系列普通存储不能形成依赖链).

    更糟糕的是,上面的write方法完全是线程不安全的.处理"独立"布尔值的两个线程可能会相互冲突,因此运行时必须使用原子更新操作,只为该对象无法在线程本地证明的任何字段写入一点.

  • 数组之外的空间节省通常非常小,并且通常为零:对齐关注意味着单个位通常最终会占用堆栈上的字节或对象的布局中的相同空间.只有boolean在堆栈或对象上有许多原始值时才能看到节省(例如,对象通常与8字节边界对齐,所以如果你有一个非布尔字段int或更大的对象,你就会需要至少4个boolean值来节省空间,通常你需要8).

  • 这为boolean数组中的位表示留下了最后剩余的"大赢" boolean,在这里你可以为大型数组节省8倍的空间.事实上,这个案例在C++世界中足够激励,vector<bool>有一个"特殊"的实现,每个都bool需要一点 - 由于所有必需的特殊情况和非直观的行为(并且经常用作现在无法删除的错误功能的示例).

    如果它不是用于内存模型,我可以想象一个Java boolean以逐位方式实现数组的世界.它们没有相同的问题vector<bool>(主要是因为JIT提供了额外的缩减层,也因为数组提供了比vector它更简单的接口),我认为它可以有效地完成.但是有一种讨厌的记忆模型.如果由不同的线程完成,该模型允许对不同数组元素的写入是安全的(即,它们作为内存模型的独立变量).如果您实现boolean为一个字节,所有常见的CPU都会直接支持这一点,因为它们具有独立的字节访问.没有CPU提供独立的位访问:你使用原子操作(x86提供lock bt*操作,但这些操作很慢:其他平台的选项更糟).这会破坏作为位数组实现的任何布尔数组的性能.

最后,如上所述,实施boolean有点显着的缺点 - 但是好处呢?

事实证明,如果用户真的想要这个布局的比特包装表示,他们可以自己做!它们可以将8个布尔值打包到对象中的一个byte(或32个值到一个int或其他任何值)(这对于标志等是常见的),并且生成的访问器代码应该高效,就像JVM本身支持的布尔值一样高效位.事实上,当你知道你想要一个大量布尔值的数组位表示时,你可以简单地使用BitSet- 这具有你想要的表示,并通过不提供任何线程安全保证来回避原子问题.因此,通过实现boolean作为一个字节,您可以回避上述所有问题,但仍然允许用户"选择加入"到位级表示,如果他们想要的话,没有太多的运行时惩罚.

  • @ flow2k - 正确.`boolean`本身仍然是一个字节(并作为一个字节访问),但由于对象被舍入到下一个对齐边界,向对象添加`boolean`通常与添加说'int`具有相同的效果.例如,Java对象通常在64位平台上具有8字节对齐,因此如果您获取一个正好是16个字节的对象并添加一个"boolean",它将是24个字节. (2认同)
  • 8 字节对齐也适用于大多数 32 位 JVM。 (2认同)