- 对象本身的大小(内存开销)
- 的大小(占用的内存)
- 如何计算和测量
- 影响大小的因素
对象本身的大小(内存开销)
一个空的 String 对象(new String())并不是完全不占空间的,在 Java 中,每个对象都有一些固定的元数据开销。

32位 JVM vs. 64位 JVM
JVM 的位数会显著影响对象头的大小。
-
32位 JVM:
- 对象头: 8 bytes (Mark Word 4 bytes + 类型指针 4 bytes)
- 引用类型: 4 bytes
- 一个空 String 对象大约占用 12 bytes (对象头 8 + char[] 引用 4,需要按 8 字节对齐,所以补 4 bytes,总共 16 bytes?这里需要更精确的解释,见下文)
-
64位 JVM (默认,且开启
+UseCompressedOops):- 对象头: 12 bytes (Mark Word 8 bytes + 类型指针 4 bytes,因为压缩了)
- 引用类型: 4 bytes
- 一个空 String 对象的大小计算如下:
- 对象头: 12 bytes
value字段 (char[] 引用): 4 byteshash字段 (int): 4 bytescoder字段 (byte): 1 byteserialVersionUID(long): 8 bytesserialPersistentFields(ObjectStreamField[]): 4 bytes (引用)- 实例数据总和: 12 + 4 + 4 + 1 + 8 + 4 = 33 bytes
- 对齐填充: JVM 要求对象大小必须是 8 字节的倍数,33 bytes 向上取整到 40 bytes。
- 一个空的
new String()对象在 64 位 JVM 上至少占用 40 bytes。
注意:
String类的字段可能因 Java 版本而异。hash字段在 Java 7u6 之后才被引入以缓存哈希码。coder字段是在 Java 9 中引入的,用于优化 Latin-1 字符的存储。(图片来源网络,侵删)
String 类的字段(以 Java 9+ 为例)
从 Java 9 开始,String 的内部实现发生了重大变化,不再使用 char[],而是使用 byte[] 来节省内存。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
private final byte coder; // 0 = LATIN1, 1 = UTF16
private int hash; // 默认为0
// ... 其他字段
}
一个 String 对象的固定内存开销包括:
- JVM 对象头
byte[] value引用byte coderint hash- 其他可能的字段(如序列化相关字段)
无论字符串内容多长,String 对象本身的固定开销至少是 40 bytes(在典型的 64 位 JVM 上)。
的大小
这部分是动态的,取决于字符串的长度和字符编码。

Java 8 及之前版本 (使用 char[])
char[]数组本身有对象头(12 bytes)和长度字段(4 bytes),共 16 bytes 的开销。- 每个字符在
char[]中占用 2 bytes。 - *总大小 = 40 (String对象) + 16 (char[]对象) + 2 字符串长度**
Java 9 及之后版本 (使用 byte[] 和 coder)
这是 Java 对 String 内存优化的一个重要改进。String 会根据内容自动选择编码。
-
coder == 0(LATIN1 编码):- 适用于所有字符都在 ISO-8859-1 范围内的字符串(如英文字母、数字、常见符号)。
- 每个字符在
byte[]中只占用 1 byte。 byte[]数组对象的开销是 16 bytes (对象头 12 + 长度 int 4)。- *总大小 ≈ 40 (String对象) + 16 (byte[]对象) + 1 字符串长度**
-
coder == 1(UTF16 编码):- 适用于包含非 Latin1 字符的字符串(如中文字符、表情符号、某些特殊符号)。
- 这种情况和 Java 8 一样,每个字符在
byte[]中占用 2 bytes。 byte[]数组对象的开销是 16 bytes。- *总大小 ≈ 40 (String对象) + 16 (byte[]对象) + 2 字符串长度**
示例对比 (在 64 位 JVM 上):
-
String s1 = "Hello";(5个 Latin1 字符)- String 对象: 40 bytes
- byte[] 对象: 16 bytes
- 内容: 5 * 1 = 5 bytes
- 总计 ≈ 61 bytes
-
String s2 = "你好";(2个 UTF16 字符)- String 对象: 40 bytes
- byte[] 对象: 16 bytes
- 内容: 2 * 2 = 4 bytes
- 总计 ≈ 60 bytes
-
String s3 = new String();(空字符串)- 总计 = 40 bytes
如何计算和测量
手动估算
根据上面的公式,你可以根据你的 Java 版本和字符串内容进行估算。
使用工具测量
最准确的方法是使用专门的工具。
jcmd (JDK 自带)
这是在运行时分析 JVM 的利器。
# 1. 找到你的 Java 进程 ID (PID) jps # 2. 使用 jcmd 查看该进程的 GC 根和类的详细信息 # <PID> GC.class_histogram 会打印所有类的实例数量和总大小 jcmd <PID> GC.class_histogram # 输出示例 (部分) ... num #instances #bytes class name -------------------------------------- 1: 5000 200000 [C (char[]) 2: 1000 40000 java.lang.String ...
这里的 #bytes 就是该类所有实例占用的总堆内存大小。
VisualVM (JDK 自带图形化工具)
- 在
bin目录下运行visualvm.exe。 - 连接到你的正在运行的 Java 应用。
- 在左侧面板选择你的应用,然后点击 "Sampler" (采样器) 标签页。
- 点击 "Heap" 按钮,然后点击 "Start" 开始堆采样。
- 让你的应用运行一段时间,执行一些字符串操作。
- 点击 "Stop" 停止采样。
- 在 "Classes" 视图中,你可以按 "Size" 列排序,查看
java.lang.String占用了多少内存,以及它的实例数量。
Java Object Layout (JOL) 工具
这是一个非常强大和流行的第三方库,专门用于分析对象内存布局。
添加 Maven 依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
使用示例代码:
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class StringSizeExample {
public static void main(String[] args) {
System.out.println("当前JVM信息: " + VM.current().details());
System.out.println("=====================================");
// 1. 空字符串
String emptyString = new String();
System.out.println("空 String 对象的内存布局:");
System.out.println(ClassLayout.parseInstance(emptyString).toPrintable());
System.out.println("空 String 对象总大小: " + VM.current().addressSize() * ClassLayout.parseInstance(emptyString).instanceSize() + " bytes");
System.out.println("=====================================");
// 2. Latin1 字符串
String latin1String = "Hello";
System.out.println("Latin1 String 对象的内存布局:");
System.out.println(ClassLayout.parseInstance(latin1String).toPrintable());
System.out.println("Latin1 String 对象总大小: " + VM.current().addressSize() * ClassLayout.parseInstance(latin1String).instanceSize() + " bytes");
System.out.println("=====================================");
// 3. UTF16 字符串
String utf16String = "你好";
System.out.println("UTF16 String 对象的内存布局:");
System.out.println(ClassLayout.parseInstance(utf16String).toPrintable());
System.out.println("UTF16 String 对象总大小: " + VM.current().addressSize() * ClassLayout.parseInstance(utf16String).instanceSize() + " bytes");
}
}
JOL 会非常详细地打印出对象的每个字段、偏移量、对齐填充等信息,是学习和理解对象内存布局的最佳工具。
影响大小的因素总结
| 因素 | 描述 | 影响 |
|---|---|---|
| JVM 架构 (32/64位) | 64位 JVM 的指针和对象头通常更大。 | 64位 JVM 上的对象固定开销更大。 |
| JVM 参数 | -XX:+UseCompressedOops (压缩普通对象指针) 会将引用从 8 bytes 压缩到 4 bytes,显著减少内存占用。 |
开启压缩后,对象引用和对象头会变小,String 对象的固定开销从 56 bytes 降到 40 bytes 左右。 |
| Java 版本 | Java 9 引入了 byte[] 和 coder 字段,对 Latin1 字符串进行了内存优化。 |
Java 9+ 对于纯英文等 Latin1 字符串,内存占用减半。 |
| 字符串的长度和字符类型(Latin1 vs. UTF16)直接决定了内部数组的大小。 | 长度越长,占用越大;包含中文字符等比纯英文字符占用更多。 | |
| 字符串池 | 使用字面量创建的字符串(如 "hello")可能会被放入字符串池,同一个字符串在内存中只有一份。 |
这可以极大地节省内存,但 String 对象本身的固定大小依然存在。 |
希望这个详细的解释能帮助你全面理解 Java String 的大小!

