java中String占用内存计算

814 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

前言

最近做项目的时候,从redis中取list使用lrange key 0 -1全部取比较方便,但是需要考虑到list过长会导致内存溢出的情况,所以就需要明确的计算下内存大小,心里才有底,不然就要改成分页取了(因为业务原因,会比较麻烦)。

需要清楚的

比较习惯计算mysql的占用内存,比如char(n),使用utf-8编码就直接按照中文占用的字节(一般3个),就直接3*n。但是首先忽略了java string的编码方式,其次java的string是个对象需要考虑对象头、类成员所占的字节。

String的编码方式

很多网上资料都说是Unicode,确实没错,但是具体是utf-8还是utf-16或者是utf-32呢?其实String的源码注释里面就有:

A String represents a string in the UTF-16 format in which supplementary characters are represented by surrogate pairs (see the section Unicode Character Representations in the Character class for more information). Index values refer to char code units, so a supplementary character uses two positions in a String.

采用的是utf-16进行编码,也就是占用固定的两个字节,超过\U200000范围的码点就用两个utf-16表示,下面我们来大概了解下。

unicode

Unicode标准为多语言纯文本的编码提供了一致的方式,并为难以在国际上交换文本的混乱状态带来秩序。

unicode标准和ISO/IEC 10646支持使用通用字符库的三种编码形式(utf-8、utf-16、utf-32,所有三种编码形式都对相同的通用字符库进行编码,并且可以有效地相互转换而不会丢失数据,所有三种编码形式每个字符最多需要 4 个字节(或 32 位)的数据。)。这种编码形式允许编码多达一百万个字符。这满足所有已知的编码要求,包含全世界上所有的历史文字,以及常见的符号系统。

utf-8

utf-8在HTML和类似协议中很流行。UTF-8是一种将所有Unicode字符转换为可变长字节编码的方法。它的优点是与熟悉的ASCII集合对应的Unicode字符具有ASCII相同的字节值,并且转换为 UTF-8 的 Unicode 字符可以与许多现有软件一起使用,而无需大量的软件重写。

变长规则如下:

码点的位数码点起值码点终值字节序列Byte 1Byte 2Byte 3Byte 4
7U+0000U+007F10xxxxxxx
11U+0080U+07FF2110xxxxx10xxxxxx
16U+0800U+FFFF31110xxxx10xxxxxx10xxxxxx
21U+10000U+1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxx

比如“世”对应的码点是19990,按照utf-8编码的规则就是\xe4\xb8\x96对应的二进制就是11100100-10111000-10010110。

utf-16

只要不大于\U200000范围的字符,固定使用两个字节表示,如果超过就用两个utf-16即4个字节表示。

utf-32

固定使用4个字节表示。

String对象头、类成员所占的字节

对象头

image.png

对象头由Mark Word和Klass组成,12个字节(压缩了指针)。

  • Mark Word:标记字段-运行时数据,如哈希码、GC信息以及锁信息。
  • Klass:对象锁代表的类的元数据指针。

image.png

类成员

这里我们不看静态成员因为归类所有,不计在对象内

private final char value[];

/** Cache the hash code for the string */
private int hash; // Default to 0

hash占4个字节,value引用占4个字节(压缩了指针)。

计算公式

12(对象头)+8(类成员)+2n(一个字符占两个字节不考虑大于\U200000范围的字符)=20+2n

还需要考虑内存对齐填充的字节,8的倍数

示例

笔者在“前言”中提到的计算从redis中取出来的list装在List<String>中占用的内存就可以按照我们的出的公式计算。

笔者redis中list元素的平均长度为50个字符,因此平均一个元素消耗的大小等于:20+2*50=120(这里不需要填充)。

因此就是120个字节,1M能放8738个元素,10M能放87380个元素,目前业务不允许挤压这么多元素,因此可以全部取出,暂时不用太担心。

参考

The Unicode® Standard: A Technical Introduction

Unicode与UTF-8/UTF-16/UTF-32的区别

utf-8,utf-16,Unicode的区别

Java中的String到底占用多大的内存空间?你所了解的可能都是错误的!!