在MySQL運行環境中,碰到內存使用完之后不釋放情況,概率還是比較高的。那有必要了解下,為什么會出現這樣的情況。
在翻看很多材料和了解下源碼實現。在早期開發InnoDB時,操作系統運行時提供的內存分配器,通常缺乏性能和可伸縮性。當時,還沒有針對多核cpu進行優化的內存分配器庫。因此,InnoDB在內存子系統中實現了自己的內存分配器。這個分配器由單個互斥鎖保護,同時InnoDB還在系統分配器(malloc和free)周圍實現了一個包裝器接口,同樣由單個互斥鎖保護。隨著多核系統的廣泛應用,以及操作系統的成熟,操作系統提供的內存分配器也有了顯著的改進。與過去相比,這些新的內存分配器性能更好,可擴展性更強。大多數工作負載,特別是那些經常分配和釋放內存的工作負載,都可以從使用高度調優的內存分配器中獲益,而不是使用內部的innodb的內存分配器,這可能成為瓶頸。
Linux操作系統內存分配方式
在Linux操作系統下進程通過C標準庫中的內存分配函數 malloc 向系統申請內存(malloc() 并不是系統調用,也不是運算符,而是C庫里的函數,用于動態分配內存。),但真正與內核交互之間,其實還隔了一層,即內存分配管理器(memory allocator)。
目前Linux系統常見的內存分配器包括:ptmalloc(Glibc)、tcmalloc(Google)、jemalloc(FreeBSD)。

分別介紹下大致的功能:
1.ptmalloc(glibc?malloc)
GNU Libc 的內存分配器(allocator)—ptmalloc,起源于Doug Lea的malloc。由Wolfram Gloger改進得到可以支持多線程。
內存分配器中只有一個主分配區(main arena),每次分配內存都必須對主分配區加鎖,分配完成后釋放鎖,在SMP多線程環境下,對主分配區的鎖的爭用很激烈,嚴重影響了malloc的分配效率。ptmalloc增加了動態分配區(dynamic arena),主分配區與動態分配區用環形鏈表進行管理。每一個分配區利用互斥鎖(mutex)使線程對于該分配區的訪問互斥。每個進程只有一個主分配區,但可能存在多個動態分配區,ptmalloc根據系統對分配區的爭用情況動態增加動態分配區的數量,分配區的數量一旦增加,就不會再減少了。而動態分配區每次使用mmap()向操作系統“批發HEAP_MAX_SIZE大小的虛擬內存,如果內存耗盡,則會申請新的內存鏈到動態分配區heap data的“strcut malloc_state”。如果用戶請求的大小超過HEAP_MAX_SIZE,動態分配區則會直接調用mmap()分配內存,并且當free的時候調用munmap(),該類型的內存塊不會鏈接到任何heap data。

Chunk說明:
一個arena 中最頂部的 chunk 被稱為「top chunk」。它不屬于任何 bin 。當所有 bin 中都沒有合適空閑內存時,就會使用 top chunk 來響應用戶請求。當top chunk 的大小比用戶請求的大小小的時候,top chunk 就通過 sbrk(main arena)或 mmap( thread arena)系統調用擴容。

優點:
- ptmalloc是GNU C庫(glibc)中的默認內存分配器,廣泛用于Linux系統。
- 基于Doug Lea的malloc實現,采用了多種技術,如自由鏈表、分離器和堆的延遲綁定等。
- ptmalloc的特點是成熟、穩定,并且與GNU C庫緊密集成。
缺點:
- 如果后分配的內存先釋放,無法及時歸還系統。因為ptmalloc收縮內存是從top chunk開始,如果與top chunk相鄰的 chunk不能釋放, top chunk 以下的 chunk 都無法釋放。
- 內存不能在線程間移動,多線程使用內存不均衡將導致內存浪費。
- 每個chunk至少8字節的開銷很大。
- 不定期分配長生命周期的內存容易造成內存碎片,不利于回收。
- 加鎖耗時,無論當前分區有無耗時,在內存分配和釋放時,會首先加鎖。
- 從上述來看ptmalloc的主要問題其實是內存浪費、內存碎片、以及加鎖導致的性能問題。
2.tcmalloc(Google malloc)
tcmalloc是Google開發的內存分配器,在Golang、Chrome中都有使用該分配器進行內存分配。有效的優化了ptmalloc中存在的問題。
tcmalloc是專門對多線并發的內存管理而設計的,tcmalloc主要是在線程級實現了緩存,使得用戶在申請內存時大多情況下是無鎖內存分配。整個 TCMalloc 實現了三級緩存,分別是ThreadCache(線程級緩存),Central Cache(中央緩存:CentralFreeeList),PageHeap(頁緩存),最后兩級需要加鎖訪問

特點:
- tcmalloc是Google開發的內存分配器,主要用于Google的C++代碼。
- tcmalloc通過減少鎖的競爭和減少內存碎片來提高性能。
- 它使用線程本地緩存(Thread-Caching Malloc)的概念,將內存分配的任務分散到不同的線程中,以減少對共享數據結構的競爭。
- tcmalloc還有其他一些優化策略,如小對象合并、高效的分配器緩存等。
tcmalloc也帶來了一些問題,使用自旋鎖雖然減少了加鎖效率,但是如果使用大內存較多的情況下,內存在Central Cache或者Page Heap加鎖分配。而tcmalloc對大小內存的分配過于保守,在一些內存需求較大的服務(如推薦系統),小內存上限過低,當請求量上來,鎖沖突嚴重,CPU使用率將指數暴增。
3.jemalloc
jemalloc是facebook推出的,目前在firefox、facebook服務器、android 5.0 等服務中大量使用。 jemalloc最大的優勢還是其強大的多核/多線程分配能力. 以現代計算機硬件架構來說, 最大的瓶頸已經不再是內存容量或cpu速度, 而是多核/多線程下的lock contention(鎖競爭). 因為無論CPU核心數量如何多, 通常情況下內存只有一份. 可以說, 如果內存足夠大, CPU的核心數量越多, 程序線程數越多, jemalloc的分配速度越快。
jemalloc解決方法是將一把global lock分散成很多與線程相關的lock。而針對多核心, 則要盡量把不同線程下分配的內存隔離開, 避免不同線程使用同一個cache-line的情況
jemalloc 按照內存分配請求的尺寸,分了 small object (例如 1 – 57344B)、 large object (例如 57345 – 4MB )、 huge object (例如 4MB以上)

- jemalloc是一款通用的內存分配器,由FreeBSD社區開發,并逐漸被其他系統廣泛采用。
- jemalloc致力于提供高度可擴展性和低碎片化的內存分配。
- 它使用了多個技術,如分離的內存區域、伙伴分配器、線程本地緩存等。
- jemalloc還提供了高級特性,如背景線程執行釋放、空間利用統計和分析等。
- 多線程下加鎖大大減少
4.小結:
性能方面:
- ptmalloc在大多數情況下性能良好,但在多線程環境下可能存在一些競爭問題。
- tcmalloc通過線程本地緩存和減少鎖競爭,適用于高并發場景,尤其是多線程服務器應用。
- jemalloc在可擴展性和碎片化方面表現出色,特別適用于大型內存分配和高負載場景。
場景方面:
- ptmalloc適用于常規應用,與GNU C庫集成緊密。
- tcmalloc適用于高并發多線程環境,通過線程本地緩存減少競爭。
- jemalloc適用于可擴展性和低碎片化要求高的場景,提供高級特性和統計信息。
總的來看,作為基礎庫的ptmalloc是最為穩定的內存管理器,無論在什么環境下都能適應,但是分配效率相對較低。而tcmalloc針對多核情況有所優化,性能有所提高,但是內存占用稍高,大內存分配容易出現CPU飆升。jemalloc的內存占用更高,但是在多核多線程下的表現也最為優異。
應該考慮好內存分配如何管理:
- 多核多線程的情況下,內存管理需要考慮內存分配加鎖、異步內存釋放、多線程之間的內存共享、線程的生命周期
- 內存當作磁盤使用的情況下,需要考慮內存分配和釋放的效率,是使用內存管理庫還是應該自己進行大對象大內存的管理。(在搜索以及推薦系統中尤為突出)。
MySQL使用的內存管理
-
從下載官方安裝軟件時,可以了解到MySQL默認使用的是 glibc的ptmalloc作為內存分配器。

-
同時在默認操作系統環境中,MySQL啟動時,默認使用glibc庫的malloc進行內存分配。
但是glibc庫的malloc存在某些問題,例如內存分配效率較低,容易出現內存碎片等問題。當然默認的內存管理,結合操作系統在不停的開發完善,內存泄漏問題,也不是常見的。對于MySQL這類高并發、高數據量的數據庫,如碰到內存不釋放的情況,并避免內存泄漏等問題,開啟jemalloc是一個不錯的選擇。
如何開啟jemalloc內存管理
1.下載安裝jemalloc內存分配器:
https://github.com/jemalloc/jemalloc/releases
#或
shell$> wget https://github.com/jemalloc/jemalloc/archive/refs/tags/5.3.0.tar.gz
#或
shell$> yum install epel-release -y
shell$> yum install jemalloc jemalloc-devel -y
#源碼安裝
shell$> ./configure
shell$> make && make install
shell$> echo "/usr/local/lib" > /etc/ld.so.conf.d/local.conf
shell$> ldconfig
2.安裝好jemalloc之后,在MySQL配置文件中啟用。修改配置文件:
#安裝jemalloc庫文件位置
shell$> ldconfig -p|grep malloc
libjemalloc.so.2 (libc6,x86-64) => /lib64/libjemalloc.so.2
libjemalloc.so (libc6,x86-64) => /lib64/libjemalloc.so
[mysqld_safe]
malloc-lib=/usr/lib64/libjemalloc.so.2
這里的路徑根據具體系統和jemalloc安裝位置有所不同,需做相應調整,之后,重啟MySQL服務即可生效。
開啟jemalloc能夠有效地提高MySQL的性能和穩定性,特別是在高并發的情況下。如果你對MySQL的性能有更高的要求,可以嘗試使用更加高級的內存管理工具。
總結
當然官方都是安裝操作系統做過很多適配測試,所以默認使用內存分配機制沒問題。如碰到內存泄漏,無法修改源頭語句,或則 MySQL軟件升級。可以采取jemalloc方式(但未必100%有用)。




