写点什么

Java 中的 String 类型到底占用多大的内存空间?

用户头像
冰河
关注
发布于: 2021 年 02 月 26 日
Java中的String类型到底占用多大的内存空间?

写在前面


对于 Java 中的 String 类占用多大的内存空间这个问题,是最近面试中问的比较多的一个问题。很多小伙伴的回答的都不是很正确,有说不占空间的,有说 1 个字节的,有说 2 个字节的,有说 3 个字节的,有说不知道的,更让人哭笑不得的是竟然还有人说是 2 的 31 次方。那如果真是这样的话,服务器的内存空间还放不下一个字符串呀!作为程序员的我们,可不能闹这种笑话呀。今天,我们就一起来聊聊 Java 中的 String 到底占用多大的内存空间!


Java 对象的结构


首先,我们来下 Java 对象在虚拟机中的结构,这里,以 HotSpot 虚拟机为例。



<p align="right">注:图片来源http://r6d.cn/wp7q</p>


从上面的这张图里面可以看出,对象在内存中的结构主要包含以下几个部分:


  • Mark Word(标记字段):对象的 Mark Word 部分占 4 个字节,其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。

  • Klass Pointer(Class 对象指针):Class 对象指针的大小也是 4 个字节,其指向的位置是对象对应的 Class 对象(其对应的元数据对象)的内存地址

  • 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte 和 boolean 是 1 个字节,short 和 char 是 2 个字节,int 和 float 是 4 个字节,long 和 double 是 8 个字节,reference 是 4 个字节

  • 对齐:最后一部分是对齐填充的字节,按 8 个字节填充。


换种说法就是:


  • 对象头(object header):8 个字节(保存对象的 class 信息、ID、在虚拟机中的状态)

  • Java 原始类型数据:如 int, float, char 等类型的数据

  • 引用(reference):4 个字节

  • 填充符(padding)


Java 中的 String 类型


空 String 占用的空间


这里,我们以 Java8 为例进行说明。首先,我们来看看 String 类中的成员变量。


/** The value is used for character storage. */private final char value[]; /** Cache the hash code for the string */private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */private static final long serialVersionUID = -6849794470754667710L;
复制代码


在 Java 里数组也是对象,因此数组也有对象头。所以,一个数组所占的空间为对象头所占的空间加上数组长度加上数组的引用,即 8 + 4 + 4= 16 字节 。


所以,我们可以得出一个空 String 对象所占用的内存空间,如下所示。


对象头(8 字节)+ 引用 (4 字节 )  + char 数组(16 字节)+ 1个 int(4字节)+ 1个long(8字节)= 40 字节
复制代码


所以,小伙伴们,你们的回答正确吗?


非空 String 占用的空间


如果 String 字符串的长度大于 0 的话,我们也可以得出 String 占用内存的计算公式,如下所示。


40 + 2 * n
复制代码


其中,n 为字符串的长度。


这里,可能有小伙伴会问,为什么是 40 + 2 n 呢?这是因为 40 是空字符串占用的内存空间,这个我们上面已经说过了,String 类实际上是把数据存储到 char[]这个成员变量数组中的,而 char[]数组中的一个 char 类型的数据占用 2 个字节的空间,所以,只是 String 中的数据就会占用 2 n(n 为字符串的长度)个字节的空间,再加上空字符串所占用的 40 个字节空间,最终得出一个字符串所占用的存储空间为: 40 + 2 * n (n 为字符串长度)。


因此在代码中大量使用 String 对象时,应考虑内存的实际占用情况。


注:40 + 2 * n 这个公式我们可以看成是计算 String 对象占用多大内存空间的通用公式。


验证结论


接下来,我们就一起来验证下我们上面的结论。首先,创建一个 UUIDUtils 类用来生成 32 位的 UUID,如下所示。


package io.mykit.binghe.string.test;
import java.util.UUID;
/** * @author binghe * @version 1.0.0 * @description 生成没有-的UUID */public class UUIDUtils { public static String getUUID(){ String uuid = UUID.randomUUID().toString(); return uuid.replace("-", ""); }}
复制代码


接下来,创建一个 TestString 类,在 main()方法中创建一个长度为 4000000 的数组,然后在数组中放满 UUID 字符串,如下所示。


package io.mykit.binghe.string.test;
import java.util.UUID;
/** * @author binghe * @version 1.0.0 * @description 测试String占用的内存空间 */public class TestString{ public static void main(String[] args){ String[] strContainer = new String[4000000]; for(int i = 0; i < 4000000; i++){ strContainer[i] = UUIDUtils.getUUID(); System.out.println(i); } //防止程序退出 while(true){
} }}
复制代码


这里,4000000 个字符串,每个字符串的长度为 32,所以保存字符串数据所占用的内存空间为:(40 + 32 2) 4000000 = 416000000 字节,约等于 416MB。


我们使用 Jprofiler 内存分析工具进行分析:



可以看到,使用 Jprofiler 内存分析工具的结果为:321MB + 96632KB,约等于 417MB。之所以使用 Jprofiler 内存分析工具得出的结果比我们计算的大些,是因为在程序实际运行的过程中,程序内部也会生成一些字符串,这些字符串也会占用内存空间!!


所以,使用 Jprofiler 内存分析工具得出的结果符合我们的预期。


好了,今天就到这儿吧,我是冰河,大家有啥问题可以在下方留言,也可以加我微信:sun_shine_lyz,我拉你进群,一起交流技术,一起进阶,一起牛逼~~


发布于: 2021 年 02 月 26 日阅读数: 21
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

Mykit系列开源框架发起者、核心架构师和开发者,《海量数据处理与大数据技术实战》与《MySQL开发、优化与运维实战》作者。【冰河技术】微信公众号作者。

评论

发布
暂无评论
Java中的String类型到底占用多大的内存空间?