Java 性能优化之 String 篇2013-09-22 IBM 杨博文,应乐年,杨雯雯String 在 JVM 的存储结构一般而言,Java 对象在虚拟机的结构如下:对象头(object header):8 个字节Java 原始类型数据:如 int, float, char 等类型的数据,各类型数据占内存如 表 1. Java 各数据类型所占内存.引用(reference):4 个字节填充符(padding)表 1. Java 各数据类型所占内存

然而,一个 Java 对象实际还会占用些额外的空间,如:对象的 class 信息、ID、在虚拟机中的状态。在 Oracle JDK 的 Hotspot 虚拟机中,一个普通的对象需要额外 8 个字节。如果对于 String(JDK 6)的成员变量声明如下:
private final char value[]; private final int offset; private final int count; private int hash;
那么因该如何计算该 String 所占的空间?首先计算一个空的 char 数组所占空间,在 Java 里数组也是对象,因而数组也有对象头,故一个数组所占的空间为对象头所占的空间加上数组长度,即 8 + 4 = 12 字节 , 经过填充后为 16 字节。那么一个空 String 所占空间为:对象头(8 字节)+ char 数组(16 字节)+ 3 个 int(3 × 4 = 12 字节)+1 个 char 数组的引用 (4 字节 ) = 40 字节。因此一个实际的 String 所占空间的计算公式如下:8*( ( 8+2*n+4+12)+7 ) / 8 = 8*(int) ( ( ( (n) *2 )+43) /8 )其中,n 为字符串长度。案例分析在我们的大规模文本分析的案例中,程序需要统计一个 300MB 的 csv 文件所有单词的出现次数,分析发现共有 20,000 左右的唯一单词,假设每个单词平均包含 15 个字母,这样根据上述公式,一个单词平均占用 75 bytes. 那么这样 75 * 20,000 = 1500000,即约为 1.5M 左右。但实际发现有上百兆的空间被占用。 实际使用的内存之所以与预估的产生如此大的差异是因为程序大量使用 String.split() 或 String.substring()来获取单词。在 JDK 1.6 中 String.substring(int, int)的源码为:
public String substring(int beginIndex, int endIndex) {if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex);}if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex);}return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); }调用的 String 构造函数源码为:
String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }