起因
下面這段代碼執(zhí)行后,內(nèi)存有增無減,增加了200M,iOS平臺200M不能接受了
// STL 集合類
void test1() {
list<int> mList;
for (int i=0; i<1000000; i++) {
mList.push_back(i);
}
mList.clear();
}
// mList 作用域 {} 內(nèi),stack 上的變量由編譯器出了 } 自動釋放
STL 底層是用 new/delete 分配內(nèi)存的,new/delete 是基于 malloc/free 分配的,malloc/free 又是基于各個操作系統(tǒng)統(tǒng)一封裝,于是我寫了下面的測試代碼
// 申請 100w 個 100byte 的空間,再釋放掉
void test2() {
char **ptr = (char **)malloc(1000000 * sizeof(char *));
for (int i=0; i<1000000; i++) {
*(ptr + i) = (char *)malloc(100 * sizeof(char));
}
for (int i=0; i<1000000; i++) {
free(*(ptr + i));
}
free(ptr);
}
內(nèi)存依然去到了300M,無減少。 原因:
- Windows 平臺調(diào)用free,內(nèi)存會馬上降回來。
- Linux 平臺調(diào)用free,內(nèi)存不會釋放回OS,而是釋放回系統(tǒng)的內(nèi)存緩沖池,進程退出時才釋放回OS。(ps:Linux下谷歌有個 tcmalloc 能做到立刻釋放到OS,或者malloc_trim(0))
- iOS 平臺調(diào)用 free 后,也只是釋放到系統(tǒng)的內(nèi)存緩沖池里,進程退出才釋放回OS。
這就很麻煩了,iOS平臺不能去到太高的內(nèi)存,不然進程會被kiil的,必須要手動釋放。
查了一下,iOS用不了谷歌的tcmalloc,malloc_trim也用不了,只能用 malloc_zone_t 自定義一個緩存區(qū),用完自己銷毀,就能夠釋放內(nèi)存。如下面這段代碼,就能把內(nèi)存釋放會 OS 了。
// 自定義 malloc_zone_t 內(nèi)申請/釋放內(nèi)存
void test3() {
malloc_zone_t *my_zone = malloc_create_zone(0, 0); // 創(chuàng)建一個 zone
char **ptr = (char **)my_zone->malloc(my_zone, 1000000 * sizeof(char *));
for (int i=0; i<1000000; i++) {
*(ptr + i) = (char *)my_zone->malloc(my_zone, 104 * sizeof(char));
}
for (int i=0; i<1000000; i++) {
my_zone->free(my_zone, *(ptr + i));
}
my_zone->free(my_zone, ptr); // 內(nèi)存釋放回 zone,并不是釋放會OS,內(nèi)存還是占用著
malloc_destroy_zone(my_zone); // 內(nèi)存釋放回 os 層,內(nèi)存占用減少
}
現(xiàn)在能搞定的是通過創(chuàng)建 zone 內(nèi)的 malloc/free 能控制內(nèi)存釋放回 OS,又有一個問題來了,STL C++的類不能用 malloc/free,內(nèi)存依然不能釋放會OS,這個問題還在找辦法,有人知道麻煩告訴我,thanks。
原理
操作系統(tǒng)管理內(nèi)存的方式進化:段式 -> 頁式 -> 段頁式。
段式有內(nèi)存外部碎片,內(nèi)存利用率低,于是發(fā)明了頁式,頁式有內(nèi)部碎片。同時頁式管理中,進程不一定要全部在內(nèi)存當(dāng)中了,不在的部分是虛擬內(nèi)存,進程的物理空間也不一定連續(xù),虛擬地址通過頁表能算出物理地址,不在內(nèi)存中產(chǎn)生缺頁中斷。。。等等
頁式管理中,一個頁為4KB,只能按頁為單位拿內(nèi)存。假設(shè)一個對象只有100 byte,為一個對象分配一個頁的內(nèi)存,有3KB多是浪費的,浪費的部分叫做內(nèi)部碎片。一個進程有幾十萬個對象,碎片就非常多了利用率低。
于是發(fā)明內(nèi)存緩沖池,假如申請的內(nèi)存大于一個頁的4KB,那么去OS申請。
假如申請的內(nèi)存都是很小的,幾百字節(jié)以內(nèi)的,那么在緩沖池內(nèi)申請。釋放的時候,只釋放回緩沖池,預(yù)防下次還要申請。也就是從OS拿部分內(nèi)存自己進程持有,由自己負(fù)責(zé)分配。
緩沖池內(nèi)存管理算法:空閑鏈表,隱式的空閑鏈表,位圖。
經(jīng)過上面的代碼測試,
默認(rèn)緩沖池直接malloc/free,速度 70ms,但內(nèi)存單調(diào)增長。
創(chuàng)建和銷毀緩沖池,自己的緩沖池內(nèi)malloc/free,速度 160ms,但內(nèi)存不增長。
所以緩沖池的出現(xiàn)體現(xiàn)了時間換空間,空間換時間的計算思維。
緩沖池的好處:
1.不用觸發(fā)系統(tǒng)調(diào)用,速度快,2.減少內(nèi)存碎片,提高利用率。
如果不適當(dāng)釋放內(nèi)存,容易導(dǎo)致內(nèi)存單調(diào)增長。

iOS 的 OC 對象都是通過 alloc 方法創(chuàng)建的,alloc 方法調(diào)用了 allocWithZone,這個 NSZone 底層就是 malloc_zone_t,就是緩沖池。所以給對象分配內(nèi)存的速度是很快的。