无处不在的常量池

引言

在看redis源码的过程中,看到了redis为了高效使用内存,内部维护了个Integer常量池,使得redis对于频繁出现的Integer,能够减少内存的使用,也减少了内存申请和释放的开销,所以把各种场景的常量池做次归纳。

各种常量池

Java常量池

JVM字符串常量池

这个太多常见了,直接google即可

Integer常量池

对于Intger常量池,可以看下面这个例子,a和b变量都是引用类型,但进行“==”判断是否是同个引用的时候返回有时会返回true,这个是什么原因呢?因为JVM内部自己维护了个常量池,a和b都是指向了相同的“常量”实例,所以”==”会返回true, 而cd的值超出了默认常量值的范围,所以“==”返回false.

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception {

Integer a = 1;
Integer b = 1;
System.out.println(a == b); //true

Integer c = 128;
Integer d = 128;
System.out.println(c == d); //false,默认情况,假如修改了jvm参数设置常量池大小,那么该结果有可能为true
}

那在jdk里面是如何使用到这个常量池的?
源码,从Integer.valueOf()方法中可以看出这里是使用了cache,也就是Integer常量值,对于 Integer a = 1这种statement, jvm其实也就是调用了valueOf方法进行boxing. IntegerCache.high是可以通过设置jvm参数进行改变。

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

所以以下这种坏味道的代码,也不会造成明显的性能影响

1
2
3
4
for (Integer i = 0; i < 10; i++){
//通常会有很多言论说这种代码会创建额外的Integer对象,但其实不然,因为
//jvm已经在背后做了优化
}

Redis 常量池

通常会遇到redis的一种使用场景,只是想通过设置某个key,然后通过判断某个key是否存在,value的值没任何意义,这个时候就会纠结value应该取什么值比较省内存,如果通过redis-rdb-tools进行内存计算,可以得出以下结果,空字符串的value内存占用比保存’1’占用了多8个字节的长度,这是因为对于’’需要保存一个空字符串,而对于’1’,则命中了redis整形常量池,无需而外分配内存保存’1’,整个数据存储的占用等另开一篇文章说明。

1
2
set k '' #总共占用56字节
set k 1 #总共占用48字节

Redis在编码字符串是会尝试进行一些压缩编码,以下代码就是redis使用整形常量池的片段

常量池初始化

1
2
3
4
5
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
shared.integers[j] =
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
shared.integers[j]->encoding = OBJ_ENCODING_INT;
}

常量池使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (len <= 20 && string2l(s,len,&value)) {
/* This object is encodable as a long. Try to use a shared object.
* Note that we avoid using shared integers when maxmemory is used
* because every object needs to have a private LRU field for the LRU
* algorithm to work well. */
if ((server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS)//redis会初始化 0至OBJ_SHARED_INTEGERS的常量,
{
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
}
}