java基本类型的物理存储大小
在java中我们知道byte就表示一个字节,像char是两个,int是四个。
现在有这样一个问题,在32位的jvm中一个byte的实际存储大小是4个字节,int不用说也是四个,那么char是不是也是四个?怎么证明?
楼主的先入观点把几种不同的概念混为一谈了。
=================================================
1、数据类型的有效宽度
JVM规范规定的是抽象的、概念中的JVM所需要支持的行为。其中对于数据类型的规定在这里:
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.2
例如说,这里说明了int的语义是32位补码的整型数,其值域为 -2147483648 到 2147483647 (-2^31 到 2^31 - 1)。
那么用更大的存储空间来存它行不行呢?当然可以,只要从上层的java代码只能感知到其中的32位是有效值即可。
char也是同理。JVM规范规定了它的语义是表示Unicode codepoint的16位无符号整型数,值域为 0 到 65535。用更大的空间来存它是没问题的,只要从上层的java代码只能看到其中16位是有效值即可。
(例子很重要所以几乎一样的话说了两遍⋯)
那么具体到存储空间,JVM规范又做了怎样的规定呢?
在JVM栈上有局部变量与操作数栈。其中局部变量的规定是:
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6.1
引用
A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress. A pair of local variables can hold a value of type long or double.
它只说了1个局部变量的slot至少要能存下boolean一直到returnAddress的值,但没规定一定要有多宽。少了不行,多了是没问题的。
操作数栈呢?
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6.2
引用
Each entry on the operand stack can hold a value of any java Virtual Machine type, including a value of type long or type double.
跟局部变量的slot的规定相似。
然后看java堆上的java对象里的字段。JVM规范说:
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.7
引用
The Java Virtual Machine does not mandate any particular internal structure for objects.
也就是说实现JVM的时候,你想如何设计Java对象在内存里的布局都没关系,只要该存的值都存着就行。跟上面一样,空间太小了不行,但多了是没问题的。
=================================================
- 实际实现使用的存储空间大小
那么真实的JVM会怎么做呢?
--------------------------------------------------------------
以解释器为主的JVM会采用比较贴近JVM规范所说的方式来组织自己的运行时数据结构。特别是入门级、简单的JVM实现更是如此。
Sun JDK 1.0.2时代的Sun JVM的做法是:它是一个32位JVM,解释器栈用32位为单位的slot来组织。JVM规范里所规定的局部变量、操作数栈的项就跟这里的slot对应。int与比int窄的整型、float、reference、returnAddress都占1个slot,long与double占两个slot。
楼主问char实际存储占多少空间,在这个JVM里char在栈上占4字节。甚至连有效数据只有1位的boolean在栈上也占4字节。
浪费JVM栈空间影响大吗?有影响,但还通常不算糟糕。
毕竟JVM栈空间主要受局部变量的个数以及方法嵌套调用的深度影响;只要嵌套调用不太深,其实用不了多少JVM栈空间。只有在深度递归或者深度嵌套调用的情况下,JVM栈空间才会显得紧缺。
然后看这个JVM如何组织java堆来存储Java对象。要分为Java类的实例和数组实例两方面来看。
它很偷懒,Java类的实例的字段同样使用32位为单位的slot来组织,字段在内存里的顺序与Class文件存的field_info顺序一致,没有重排序。这里1个char也是占了4字节,虽然其中只有2字节是有效数据。
而数组则采用紧凑布局,数组元素的宽度与其有效宽度一致(boolean除外,1个boolean占1字节但有效数据只有1位)。于是这个JVM里char[]里的char就占2字节。
浪费Java堆影响大吗?这就大了。典型的面向对象应用会产生相当大量的对象,每个小字段都浪费一点就可以浪费很多。特别是当编写Java代码的程序员原本为了省内存而用较窄的字段时,会失望的发现在这个JVM上几乎是无用功。
楼主有没有感受到原始数据类型“占多少字节”不是那么简单的事情了?
在哪个JVM实现里、存在什么地方都有关系。
--------------------------------------------------------------
那么拉到现在我们身边的现实,Oracle/Sun JDK里的HotSpot VM。
HotSpot VM有32位和64位版。楼主问的问题在这俩版本上表现不同。
另外在JVM栈的方面,解释器用的栈桢(interpreted frame)与被JIT编译后的代码用的栈桢(compiled frame)的布局也不一样。
32位版上,解释器的栈桢布局是以32位为单位的slot来存储局部变量与操作数栈项的。情况跟楼主的想像相同:1个char在局部变量与在操作数栈上都占4字节。
编译后代码的栈桢布局里,局部变量也是用32位slot来存储,没有操作数栈。
64位版上,解释器的栈桢的slot是64位的。这里1个char会占用8字节,1个boolean、short、int、float也是8字节;而1个long或double仍然占2个slot,也就是64*2=128字节。这主要是为了实现方便,代码容易与32位版统一起来。
编译后代码的栈桢里,局部变量也是用64位slot,但long和double可以只占1个slot;同样没有操作数栈。这里1个char也占8字节。
经过查证这里的64位版本解释器的栈帧slot是64位是错误的
如图我们可以看到jdk17 64版本的slot槽依然是32位
无论是在 32位 JVM 还是 64位 JVM,解释器(Interpreter)中的栈帧的 slot(槽位)都是 32 位的(4 字节)。
✅ 原因解释:
JVM 规范(java Virtual Machine Specification)中明确指出:
Each slot is 32 bits wide. A long and a double value occupy two consecutive slots.
也就是说:
每个 slot 是 32 位(4 字节)
int, float, reference, returnAddress 等都是 1 个 slot
long 和 double 占用 2 个 slot,低位在前(按 slot 顺序)
🚫 与 JVM 架构(32/64位)无关:
32位 JVM:操作系统与 JVM 都以 32 位为单位处理地址、寄存器和原生数据类型。
64位 JVM:JVM 可以访问更大的内存,但 解释器中局部变量表、操作数栈仍然以 32 位为单位分配 slot。
JVM 在内部会对对象引用使用压缩指针(Compressed Oops)优化空间,这时 reference 依旧是占用 1 个 slot(即 4 字节)。
📦 HotSpot 解释器中的实现细节:
slot 本质上是一个 int(32-bit),long 和 double 拆成两个 int 表示。
即使在 64 位 JVM 上,HotSpot 的解释器仍然坚持按照 32 位 slot 单位组织局部变量表和操作数栈。
JIT 编译器可能在编译后的机器码中采用 64 位寄存器或 native 类型优化,但解释器层面仍然是 32 位 slot。
在Java堆上,HotSpot VM采用紧凑的对象布局。字段会根据其宽度做重排序,以期尽量有效的使用内存。除了boolean占1字节外,其它数据类型的字段都占与其有效宽度相同的大小。数组也是一样。所以在HotSpot VM里Java对象的char类型字段以及char[]里的char都是占2字节。
另外还有更有趣的:在Oracle/Sun JDK 6的后期版本里有压缩字符串功能,可以用-XX:+UseCompressedStrings打开;JDK7与JDK8暂时去掉了这个功能。
本来1个java.lang.String实例会引用着1个char[]来存储实际字符串内容。当这个功能开启的时候,构造String实例时会检查里面的内容是否只有ASCII范围内的字符,如果是就用byte[]来存字符串内容,不是就退回到用char[]来存。String的用户则完全不受影响,仍然“觉得”String里装着的是一串char。
这样,表面上有6个char的1个String,背后的每个字符可能只占了1字节(因为每个字符都是存在byte[]里的一个元素)。
要留意这里是说“压缩字符串”,而不是“压缩字符”。这种实现中char仍然是UTF-16的code point,仍然至少要占2个字节的有效空间。