习惯了C与语言中精确计算一个结构体,一段数组的所占空间,在使用Java时就有些心里没底。虽然知道Integer比int要大,到底大多少?知道String比char[]要大,到底大多少?我甚至一度认为这是与JVM的GC相关的动态数字。 看了几篇针对性的文章,并做实验,有了明确的认识。
归纳成以下几个数字化的结论
一个Object需要8字节的housekeeping
一个Object最终占用的字节数要向8字节对齐,即是8的倍数
Array一是种特殊的Object,一个Array需要8字节的housekeeping+4字节的length
一个char占用2个字节
关于String,先看一下String的定义
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** Cache the hash code for the string */
private int hash; // Default to 0
一个空的String所占用的字节数, 共计40bytes。一个字符都没写,40bytes先用去了。 如果写入10个char,那就是占用40+10*2=60bytes。也由此可见,写入char的长度多大,越可以摊薄每一个char所占用的空间,毕竟这个40bytes的启动成本太重了。
housekeeping 8bytes
char value[] 4+12(reference+itself) = 16bytes
offset/count/hash 3*4=12bytes
padding 4bytes
举例,在实际工作中,经常遇到一些错误的实践,或者说省事儿的实践,就是在进行类型定义时,一切皆String。虽然性能优化不是最初该考虑的事情,但有时很快就会遇到不断调大Xmx,直至崩溃的场景。这里做一个实验,并通过JProfile来观察内存的使用情况。
Java对象序列化ObjectOutputStream和ObjectInputStream示例
String数组
String[] number_array = new String[ARRAY_SIZE];
for(int i=0; i<ARRAY_SIZE; i++){
number_array[i] = new String(i+"");
}
Integer数组
Integer[] number_array = new Integer[ARRAY_SIZE];
for(int i=0; i<ARRAY_SIZE; i++){
number_array[i] = new Integer(i);
}
int数组
int[] number_array = new int[ARRAY_SIZE];
for(int i=0; i<ARRAY_SIZE; i++){
number_array[i] = i;
}
效果
定义ARRAY_SIZE=8M
String数组: 494MB,平均每个String的空间约为66字节,符合预期。
Integer数组: 134MB,平均每个Integer的空间约为17字节,符合预期。
int数组: 40MB,(由于其内部实现为多段不连续的int[],因此加入多个array的空间成本),平均每个int的空间约为5字节,符合预期。
494MB vs 40MB, 12倍左右的差距,非常明显的改善。虽然我们在定义如同HashMap的类型时,无法直接使用int,退一步讲,对于ID类型的Key,使用Integer也要比String有明显的空间压缩。记住,一个空的String占40字节!