Redis通過自己的方法管理內存,,主要方法有zmalloc(),zrealloc(), zcalloc()和zfree(), 分別對應C中的malloc(), realloc()、
calloc()和free()。相關代碼在zmalloc.h和zmalloc.c中。
Redis自己管理內存的好處主要有兩個:可以利用內存池等手段提高內存分配的性能;可以掌握更多的內存信息,以便于Redis虛擬內存(VM)等功能中,決定何時將數據swap到磁盤。
先回憶各個系統中常見的內存分配函數:
malloc()分配一塊指定大小的內存區域,并返回指向區域開頭的指針,若分配失敗,則返回NULL。
calloc()與malloc()一樣,分配一塊指定大小的內存區域,成功時返回區域頭指針,失敗返回NULL。
區別在于, calloc()的輸入參數為count和size,即分配的項的數
目,及每一項的大小。
calloc()在成功分配內存空間后,會將空間內所有值置0。
realloc()修改已分配的內存塊的大小。若已分配的內存塊后沒有足夠的空間用于擴展內存塊,則重新申請一塊滿足需要的內存塊,并將舊的數據拷貝到新位置,釋放舊的內存塊,返回指向新的內存塊的指針;否則直接擴展原有的內存塊。若分配失敗,返回NULL。
free()釋放已分配的內存塊。
內存分配
在Redis中,如果系統中包含TCMALLOC,則會使用tc_malloc()等TCMALLOC中的方法代替malloc()等原有的分配內存方法。 TCmalloc是google perftools中的一個組件。
#if defined(USE_TCMALLOC)
#define malloc(size) tc_malloc(size)
首先看zmalloc()和zfree()兩個最常用的方法。 Redis在申請內存時,除了申請需要的size外,還會多申請一塊定長(PREFIX_SIZE)的區域用于記錄所申請的內存塊的長度。如果申請成功, Redis會使用宏函數(Redis中為性能考慮,大量使用宏函數)
update_zmalloc_stat_alloc(size+PREFIX_SIZE, size)記錄申請的內存塊的相關信息,以便監控內存使用狀況;當內存塊被zfree()釋放時,根據頭部的信息可以快速地獲知被釋放的內存區域的長度,然后通過宏函數update_zmalloc_stat_free()標記釋放。源代碼中,若系統支持malloc_size()方法,則會使用它返回指針所指向的內存塊的大小(Mac OS X 10.4以上支持該方法[3])。 有疑惑的是,在支持malloc_size()的系統中,為何還要多申請PREFIX_SIZE的內存?
?
void *zmalloc(size_t size) {void *ptr = malloc(size+PREFIX_SIZE);if (!ptr) zmalloc_oom(size);
#ifdef HAVE_MALLOC_SIZEupdate_zmalloc_stat_alloc(redis_malloc_size(ptr),size);return ptr;
#else*((size_t*)ptr) = size; // 在頭部記錄內存塊的長度update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);return (char*)ptr+PREFIX_SIZE;
#endif
}
宏update_zmalloc_stat_alloc()中,首先將要分配的空間與內存對齊,然后會根據宏zmalloc_thread_safe判斷是否需要對內存信息記錄表的相關操作加鎖。雖然Redis在大部分場景中是單線程讀寫的,即thread_safe的,但啟用虛擬內存(VM),或持久化dump到磁盤等操作時會啟動多線程,因此在多線程模式中,需要對部分操作加鎖。內存監控
used_memory記錄了Redis使用的內存總數。而多線程下malloc()是線程安全的。
zmalloc_allocations[]記錄了各個size分配的內存塊的數目,大于256個字節的按256算。應用程序可以通過zmalloc_allocations_for_size(size)獲得對應size的
內存塊的分配數目;也可以通過zmalloc_used_memory()獲得Redis占用的總內存。這些監控類的方法在Redis的日志系統中被用到。
zcalloc(size)、 zrealloc()與zmalloc()的處理策略類似,不再詳述。
在部分操作系統中, Redis可以通過zmalloc_get_rss()方法獲得自己的進程占用
的內存信息。該信息通過操作系統提供,往往比Redis自己記錄的used_memory更準確,
但其獲取速度也較慢。這些信息也是用于虛擬內存功能。
除了內存相關的操作外, Redis在此還提供了一個復制字符串的方法zstrdup(char
*),該方法將申請一塊與源字符串長度相同的內存區域,并用memcpy()拷貝字符串的內
容。
?