動不動就 32GB 以上內存的伺服器真需要關心內存碎片問題嗎?

我怎麼感覺內存碎片問題是在以前內存小的時代留傳到現代的,

現在os本身的內存管理難道對內存碎片問題一點辦法也沒有?

在網上查了下,linux好象有個什麼夥伴系統用於對付內存碎片的。

當然服務端設計,只要是分散式架構,其中一台伺服器重啟也可以做到不影響服務,是可接受的,只是想問問實際上內存碎片問題真的嚴重嗎?都32G以上內存,現代os也有應對措施。


針對下面的回答補充一下,當初採用分散式是為了避免單點故障,比如機房問題,硬體損壞之類的,不是為了內存碎片,另外伺服器都自己買的,一次性的硬體投入,沒覺得很貴,也部署了部分服務到linode,不過是為了保險,並不是主要的。

另外我是說內存碎片,不是內存泄漏,內存泄漏,那是bug,必然要解決的

也不是下面說的那麼誇張,重啟服務跟玩似的。 目前暫時沒遇到什麼大問題,但是無法保證以後有沒有問題,如果問題確實嚴重,準備考慮整體換google的內存分配器tcmalloc


1、如果編程使用的是無法直接操作內存的語言,比如 python/php/node.js 那麼真的無需關心內存碎片的問題,關心也關心不來,心有餘力不足就是說這個;
2、能夠直接操作內存的語言,要看應用場景:
2.1、編寫直接面向用戶的應用,比如寫個 game server,現在幾乎不需要關心內存碎片問題(10 年前是必備技術喲);
2.2、編寫的是一個容器,比如一種編程語言的 Runtime,比如一個應用程序運行環境(如操作系統內核/nginx/apache),就需要關心,因為每個用戶都是魔鬼,他們會用你完全想像不到的方式折磨你寫的程序,必須懷最大的惡意去揣測他們,並使用最悲觀的方案以使你的程序在極端惡劣的環境中能夠繼續運行;此外,如果你編寫的是一種特殊應用程序,業務非常單一、非常獨特,比如 memcached,那麼就值得自己編寫內存分配代碼,這樣除了可以避開內存碎片問題,還可以獲得極致性能。

綜上所述,回到題主的問題:考慮到目前已有的內存分配代碼已經非常強大(比如 tcmallac),而機器內存又十分巨大,而且只有千分之一不到的人在寫 2.2 的代碼,所以可以說現在不需要關心內存碎片問題了。但是,內存管理就像多線程編程一樣,學懂它是極好的,哪怕日後使用 php,如果心中有相關的理論知識指引,寫的 php 也會漂亮很多。


The Memory Fragmentation Problem: Solved?

1998 年的論文:CiteSeerX — The Memory Fragmentation Problem: Solved?
我的觀點是:一般的 64-bit C/C++ Linux 服務程序現在已經無需關心所謂的「內存碎片問題」了,也不必動不動就要自己去實現內存池,只需要用一個足夠好的現代 malloc 實現即可,例如 tcmalloc。這裡「一般的服務程序」指的是非內存資料庫這種以內存為主要資源的服務程序。另外,我說的服務程序是用戶態的進程,不是內核里的東西。

再補充兩篇有關定製內存分配器文獻:

Memory Allocation: Either Love it or Hate It (Or Think It』s Just OK)
http://accu.org/content/conf2008/Alexandrescu-memory-allocation.screen.pdf

Reconsidering Custom Memory Allocation
http://www.cs.umass.edu/~emery/pubs/berger-oopsla2002.pdf
http://www.cs.umass.edu/~emery/talks/OOPSLA-2002.ppt


簡單的說:虛擬地址的內存基本上不用考慮,使用實地址(物理地址)的內存需要考慮碎片

如果你的代碼是跑在應用層用戶態的,那麼內存碎片基本上(如果用太爛/太特殊的開發工具除外)是不用考慮的,用戶態的虛擬地址是基本上不用考慮碎片問題的。

如果你的代碼是跑在內核態,直接從內核里按page來申請,並且申請的是虛擬地址的話,那麼通常來說也不會遇到內存碎片的問題。

如果你的代碼是跑在內核態,並且有用到DMA/直接地址訪問等操作,那麼內存碎片是必須考慮的,這些代碼一般都涉及一些驅動的開發,並不是所有設備都能工作在高於4G的地址上,那麼4G以下的地址如果被佔滿,或者找不到足夠大的物理連續地址做DMA,那麼內存碎片問題就會出現。

儘管伺服器內存大於4G的太常見了,但是只能使用4G以下地址做DMA的硬體還是很多的,比如USB的某些控制器(OHCI/UHCI),某些網卡(fei)等,如果驅動寫的不好,或者驅動里有自己的代碼,或者用了dma_alloc之類的,就必須考慮碎片。

當然,話說回來,稀奇古怪的硬體在PC平台上越來越少了。


32G內存真的不大。
幾年前用96G的內存還是發生了很多次Redis由於內存碎片導致無法分配內存的事情。不過那會真是不太會用,碎片佔了50%,優化空間很大。


當然需要。再大的內存,只要軟體運行的時間足夠久,都有可能產生大量的內存碎片,從而對性能和可用內存造成負面影響。造成內存碎片的原因大致可以歸為兩類:

  • 內存分配機制。
    • 擁有先進GC機制的語言(如Java、C#),在對抗內存碎片方面表現較好。它們的GC一般會有個Compact步驟,會移動對象在內存中的位置,將多個對象整齊無間隙地排列好,從而消除了不少內存碎片。
    • 如果是使用傳統malloc/free或者自己寫內存分配的話,產生內存碎片的概率不小。這方面比較典型的例子就是Firefox,它以前代碼里有不少自己寫的allocator,內存碎片問題是非常嚴重的,曾經看到過一張圖(現在找不到了)描述這個問題。後來Mozilla開始逐步採用jemalloc來幫助解決這個問題。
  • 軟體的實現細節。這方面具體原因比較多樣,很難歸類,舉2個例子:
    • Firefox7的時候修改了一個內存分配行為,就一下子降低了不少內存碎片:Firefox 7 Might Solve Memory Fragmentation Issues
    • Firefox15的時候對addon的機製做了改動,一下子解決了大量長期困擾的addon內存問題:Firefox 15 plugs the add-on leaks

取決於軟體的具體類型,對抗內存碎片可能是個長期的戰爭,有興趣的可以翻翻Mozilla的MemShrink項目:MemShrink | Nicholas Nethercote 看看別人是怎麼用了2年功夫把Firefox從一個超級耗內存的瀏覽器變成一個最節約內存的瀏覽器。


只要一天不能把整個資料庫都塞進內存,那一天就還不能說內存夠用


某一個運營商的實際案例:
由於資源泄漏,內存使用量將會逐漸增大,直到系統宕機。最初他們不知道是內存泄漏,只是覺得內存不足,所以拚命的加內存。但到最後發現這事兒不成,積少成多還是會掛,查了半天還是找到了根源。


這就是為什麼我們需要分散式和雲計算,虛擬化了之後,碎片又怎樣?整個instance一起幹掉,動態增減,一兩個node掛掉,不是什麼大事


看場景吧。主要2個問題,
1、語言是否允許、需要操縱底層。語言不允許,就別操心了。
2、即使語言允許,也要看看泄漏的情況和修復的的難度。一般來說,例如C、C++遇到的內存泄漏,絕大部份是源於「不規範地申請和釋放」,例如,A申請了B釋放,該用share_ptr的沒用等等。這種修復難度不算特別高。
3、如果每次泄漏一丁點,大概1個月才會造成影響,而程序2天重啟一次可以解決,那其實不一定要修。。。著名的例子是Chrome。Chrome的內存佔用一直超級大,以前我也意味是緩存等等,直到某個release,changelog裡頭出現了「fix memory leak」……


32G的內存其實不不算大。我現在測試用的機器就已經到64G。在生產上則使用128G的機器。在內存中保存接收到的全國範圍的一天的股票買賣申報。總之不要以為電腦的資源是充足的。google推出GFS系統來就是解決大數量文件保存的問題。如果你僅僅編寫一下小規模的程序可以這麼考慮。


瀉藥。
buddy內存分配是OS做的事情,和應用層面無關。看起來LZ應該更多的是一個OS的使用者而不是開發者。

即便32GB的伺服器,長期運行以後多少都會有內存碎片的問題,主要體現在應用程序申請大塊連續內存的時候有可能失敗。但是實際作為OS使用者其實並不需要太關心這個問題。


在內存受限和內存相對充足的環境下申請和使用內存的策略是有區別的,就像回答里有人提到在的單片機或者上DSP片子的C程序寫法必然不能和普通x86應用程序一樣。換句話說,如果你的應用程序必須經常申請某個大小的整塊內存,而又因為這個總是造成應用程序任務運行失敗的時候,那這個應用程序自己就應該考慮自己的策略了,比如:

1. 申請大小為N的內存區域失敗的時候,接著申請N/2大小的區域,如此循環直到能正常分配內存,並相應的對程序進行調整使之可以在較小內存使用下分割大任務。做到對內存的自適應。

2. 如果該應用程序必須要一個size不能改變的大塊內存,那就在儘早的時候(比如在系統啟動的時候)就佔領足夠多的內存並且把持在自己手裡,其實也就是自己實現內存池了。

=====
但是不論採取什麼策略規避問題,總是要有trade-off的,比如:
策略1雖然可以解決問題,但是會消耗更多的時間,
策略2雖然可以保證內存夠用,但是需要自己管理內存池。

如果LZ認為重啟下集群中的某個伺服器不對系統產生影響,那也是個很現實的解決方案,每個機子隔上十天半月錯開時間自動重啟,寫到crontab里,問題解決。


到目前為止,我看到的所有回答,全錯。

對應用程序來說,內存碎片與操作系統無關,因為操作系統提供的內存分配函數(如HeapAlloc)只能用來分配大塊內存。小塊內存只能靠用戶級的內存分配器(如malloc)。而內存碎片的罪魁禍首就是小塊內存的頻繁分配。

對付內存碎片有三種辦法:
1. 不要動態分配內存,盡量使用棧。
2. 根據使用場景定製內存分配策略。比如如果你發現你常常分配同一種大小的數據結構,那麼就一次分配一個數組從裡面挨個取,用完了扔回旁視列表裡以備重用。再如你發現內存分配具有周期性,那麼你可以周期性的分配大塊內存,用完了釋放整塊內存。
3. 不手動管理內存,改用具有分代內存管理、增量垃圾收集和內存整理功能的垃圾收集器。比如你要是用了Java,那麼你就再也不用操心內存碎片了(代價是你的程序佔用的內存會比用C語言時翻幾倍)。

我覺得以上是內存管理的常識吧。我雖然六年沒用過C/C++了,這點常識還是知道的。


1.產生原因:內存碎片、文件系統碎片等等,其產生原因是,對固定大小的連續性資源進行無規劃的分配,就一定會產品碎片。換句話來說,無論你用何種演算法或策略,只要無規劃就一定會產生碎片。由於計算機上,不能保證100%的程序都是有規劃的分配,因此碎片問題一定會一直存在,無法避免。

2.優化方法:既然碎片問題的根源在於無規劃,那麼有規劃就能減免碎片。目前業界常用的兩個優化方法是:
----1.分頁。
----2.池。
但要注意,這些只是優化方法。優化方法能減少以及儘力規避碎片,但無法根治。

3.因此這就產生了一個問題,有些運行了很多年沒有重啟的機器,他們就沒有碎片嗎?
----1.有碎片。只不過這些機器由於內存容量充沛,充沛到幾年內碎片問題也不會影響到機器穩定性。
----2.根據業務高度定製的內存管理原則。比如:
----.----1.OS級別,給APP用多級分頁策略來分配大內存塊,當每個塊的碎片過多時,就會開闢新的塊給APP用,舊塊則回收。
----.----2.對於分散式系統來說,部分節點重啟不影響整體系統的運行。因此,整個系統看上去彷彿是多年不停機,實際上承載它們的節點伺服器早已重啟過很多次了。


一直都是在32MB可用內存的環境下考慮內存碎片問題的,真的這不是段子。
32GB內存環境下搭積木的水平應該比應用程序手動內存管理水平來得重要。


內存碎片問題實際上跟時間關係不大,跟空間關係也不大。
碎片是內存分配釋放引起的,不好的設計,要多少次內存分配就能把32g內存全都布滿碎片呢?100萬次夠了沒?如果一次請求分配10次內存,就是處理10萬次請求,那麼處理10萬次請求要多少時間呢,有的系統可能只需要幾分鐘,有的可能要幾天。空間也是如此,如果32g內存只要10分鐘就都是碎片,那麼64g也就20分鐘,128g也就40分鐘。
當然,如果空間大到一個很大很大的值,比如2的52次方,那又另外一回事情了。
比如某個內存分配演算法每次都分配4k以上,按照最小物理頁分配。那麼可以利用虛擬內存,把不連續的物理內存連續起來,那麼要佔滿2的52次方的地址空間,還是得一段時間的。2的52次方1M個32G,按照上面的10分鐘計算的話大概要100萬分鐘。但是另外一個問題,2的52次方的地址空間全部都配置分頁的話,分頁開銷就需要至少需要8192G內存,需要這麼多內存來存放分頁目錄結構。
對付碎片最好的演算法應該是slab及其變種。最大slab大小如果過大,內存利用率會很低,變種slub就只分配4K以下的,超過的就用其他內存分配。原則上4K以下的內存分配不會造成內存碎片,如果一個系統超過4k的動態分配還是很多,那麼碎片還是會有問題。可以說沒有一個內存分配演算法可以杜絕內存碎片。要解決內存碎片最好的方法還是把內存分配透明,讓程序員知道內存是怎麼分配的,才能規避內存碎片。黑盒內存分配,想靠演算法杜絕碎片是不現實的,只能說是緩解。


首先,Linux kernel 的 buddy memory allocation 是內核使用的物理內存維護演算法之一,與應用程序無關。
Glibc 的 dlmalloc [1] 演算法才是應用程序相關的,且就是 dlmalloc 的 fragmentation 被討論得很多 [2] 。對於特定應用,如果對內存的使用剛好導致 dlmalloc 的碎片過多,你不可能置之不理。
碎片的問題,一方面是浪費內存,無法分配,另一方面也是讓分配效率降低:內存越大,可能的碎片越多,每次分配查找時間也越大。沒有一個合理的內存分配策略始終是不可行的。

要說現在伺服器都有 32G 內存……呵呵呵。參考 Amazon EC2 的報價 [3][4],1.7G 內存的伺服器 6 美分一小時,34G 內存的 m2.2xlarge 82 美分一小時。

至於說集群……一台機器里能解決的問題,分配到集群上執行需要多考慮至少 50% 的複雜度問題:同步、通信、數據保持……你要是基本的內存處理都懶得解決,集群這種高級貨還是放棄了吧。

[1] C dynamic memory allocation
[2] http://gameangst.com/?p=496
[3] Amazon EC2 Pricing
[4] Amazon EC2 Instance Types


內存分配的話其實要看你用的程序api的級別
如果是使用malloc分配代碼的話,malloc其實是不同平台不同編譯器自己實現的,他有自己
的演算法,大概思想應該就是如果事先分配一些大內存,如果是需求小內存的話,從大內存中劃撥一塊給他,如果用完了,就調用系統api(windows下應該
是HeapAlloc,linux下應該是brk)來增加。理論上,這些內存應該不用擔心會碎片……這是操作系統內核的演算法保證的,而如果我們頻繁的釋放
使用malloc、free造成的內存碎片是已經分配給我們的那些內存
這就是為什麼替換tmalloc可以比較好的解決碎片問題,他實際上是複寫了c庫的malloc

下面我提供一個思路好了
nginx的內存管理: nginx的為對象提供內存使用的是ngx_malloc(好像是這個名字)函數,他其實就是先用c庫的malloc分配一大塊內存,然後自己手動管理整個內存,全部應該只用到幾次加減比較運算,還算比較簡單的解決了內存碎片問題。

最後說下 其實我是寫python的 內存碎片是什麼啊?哈哈哈


剛好近兩年處理過幾起內存碎片的問題,嘗試回答下。
注意,不是內存泄露。

先說結論:
只要程序持續有內存分配行為,就可能導致內存碎片;
內存碎片問題,隨著內存分配的釋放,只會越來越嚴重,很難緩解,表現上就是內存rss使用不斷增大;
好的內存管理演算法,如jemalloc,tcmalloc等,可以很大程度上減少內存碎片問題,但不能迴避;
關於題主說的內存32g大的情況,只是相對應用程序本身使用內存(小)而言,這和內存碎片沒有必然關係,答主之前遇到的內存碎片,都是在64g的伺服器上。

另外,順便吐槽下,linux自帶的glibc太老了:-(

手打,詳細內容,後面再補充。


聚沙成塔,集腋成裘。
我覺得是必須的。我們會對nginx進行二次開發,做一些介面類服務。因為進程本身需要長時間不重啟,同時由於訪問量大,如果經過多次、長時間的累計,必然造成內存的上漲,導致服務出問題。
因此一般還是做好內存管理的好~


SGI STL 中的二級空間配置器 已經能夠很大程度上緩解內存碎片的問題了,沒錯 SGI STL 自己就維護了一個 內存塊小於 128k 的內存池。


推薦閱讀:

程序員的鄙視鏈是什麼?
使用 Linux 的人一般是出於什麼原因選擇這個系統?
為何 Linus 一個人就能寫出這麼強的系統,中國舉全國之力都做不出來?

TAG:操作系統 | 編程 | Java | C(編程語言) | Linux 開發 |