Linux之《荒島餘生》(三)內存篇

內存問題,腦瓜疼腦瓜疼。腦瓜疼的意思,就是腦袋運算空間太小,撐的疼。本篇是《荒島餘生》系列第三篇,讓人腦瓜疼的內存篇。其餘參見:

Linux之《荒島餘生》(一)準備篇

Linux之《荒島餘生》(二)CPU篇

小公司請求量小,但喜歡濫用內存,開一堆線程,大把大把往jvm塞對象,最終問題是內存溢出。

大公司並發大,但喜歡強調HA,所以通常保留swap,最終問題是服務卡頓。

而喜歡用全局集合變數的某些同仁,把java代碼當c寫,對象塞進去但忘了銷毀,最終問題是內存泄漏。

如何避免?

合理參數、優雅代碼、禁用swap,三管齊下,

trouble shooter。

從一個故事開始

老王的疑問

一個陽光明媚的下午,一條報警簡訊彈了出來。老王微微一笑,是cpu問題,idle瞬時值,大概是某批請求比較大引起的峰值問題。老王每天都會收到這樣的簡訊,這樣的一個小峰值,在數千台伺服器中,不過是滄海一栗,繼續喝茶就是了。

但,這次不一樣。幾分鐘之後,幾百個服務的超時報警鋪天蓋地到來。事後老王算了一下,大概千分之零點幾的服務超時了,不過這已經很恐怖了。

事態升級,恐怕沒時間喝茶了。

大面積報警,應該是全局問題,是網路卡頓?還是資料庫抽風?老王挑了一台最近報警的伺服器,輪流監控了各種狀態,總結如下:

  • cpu偶爾有瞬時峰值,但load非常正常
  • 內存雖然free不多了,但cached還有不少
  • 網路各種ping,基本正常
  • 磁碟I/O一般,畢竟是服務計算節點
  • 資料庫連接池穩定,應該不是db抽風
  • swap用了不少,但好像每台機器都用了,沒啥大不了

全局性的東西不太多,網關、LVS、註冊中心、DB、MQ,好像都沒問題。老王開始腦瓜疼了。

讓老王休息一下,我們把鏡頭轉向小王。

小王的操作

小王不是老王的兒子,他是老王的徒弟。徒弟一思考,導師就發笑。這次小王用的是vim,想查找一個Exception,他打開了一個8GB的日誌文件,然後樂呵呵的在那等著載入。然後,伺服器就死了。

答案

這裡直接給出答案,原因等讀完本文自然會了解。

老王的問題最終定位到是由於某個運維工程師使用ansible批量執行了一句命令

find / | grep "x"

他是想找一個叫做x的文件,看看在哪台伺服器上。結果,這些老伺服器由於文件太多,掃描後這些文件信息都緩存到了slab區。而伺服器開了swap,操作系統發現物理內存佔滿後,並沒有立即釋放cache,導致每次GC,都和硬碟打一次交道。然後,所有服務不間歇卡頓了…

最終,只能先關閉swap分區,然後強制內核釋放cache,然後再開啟swap。當然這個過程也不會順利,因為開、關swap,同樣會引起大量I/O交換,所以不能批量去執行。這幾千台機器,是要忙活一陣嘍。

小王的問題就簡單多了。他使用vim打開大文件,所有文件的內容都會先載入到內存。結果,內存佔滿、接著swap也滿了,然後oom-killer殺死了服務進程,給一頭霧水的小王留下了個莫名其妙。

排查內存的一些命令

內存分兩部分,物理內存和swap。物理內存問題主要是內存泄漏,而swap的問題主要是用了swap~,我們先上一點命令。

(#1) 物理內存

#根據使用量排序查看RES

top -> shift + m

#查看進程使用的物理內存

ps -p 75 -o rss,vsz

#顯示內存的使用情況

free -h

#使用sar查看內存信息

sar -r

#顯示內存每個區的詳情

cat /proc/meminfo

#查看slab區使用情況

slabtop

通常,通過查看物理內存的佔用,你發現不了多少問題,頂多發現那個進程佔用內存高(比如vim等旁路應用)。meminfo和slabtop對系統的全局判斷幫助很大,但掌握這兩點坡度陡峭。

(#2) swap

#查看si,so是否異常

vmstat 1

#使用sar查看swap

sar -W

#禁用swap

swapoff

#查詢swap優先順序

sysctl -q vm.swappiness

#設置swap優先順序

sysctl vm.swappiness=10

建議關注非0 swap的所有問題,即使你用了ssd。swap用的多,通常伴隨著I/O升高,服務卡頓。swap一點都不好玩,不信搜一下《swap罪與罰》這篇文章看下,千萬不要更暈哦。

(#3) jvm

# 查看系統級別的故障和問題

dmesg

# 統計實例最多的類前十位

jmap -histo pid | sort -n -r -k 2 | head -10

# 統計容量前十的類

jmap -histo pid | sort -n -r -k 3 | head -10

以上命令是看堆內的,能夠找到一些濫用集合的問題。堆外內存,依然推薦

《Java堆外內存排查小結》

(#4) 其他

# 釋放內存

echo 3 > /proc/sys/vm/drop_caches

#查看進程物理內存分布

pmap -x 75 | sort -n -k3

#dump內存內容

gdb --batch --pid 75 -ex "dump memory a.dump 0x7f2bceda1000 0x7f2bcef2b000"

內存模型

二王的問題表象都是CPU問題,CPU都間歇性的增高,那是因為Linux的內存管理機制引起的。你去監控Linux的內存使用率,大概率是沒什麼用的。因為經過一段時間,剩餘的內存都會被各種緩存迅速佔滿。一個比較典型的例子是ElasticSearch,分一半內存給JVM,剩下的一半會迅速被Lucene索引佔滿。

如果你的App進程啟動後,經過兩層緩衝後還不能落地,迎接它的,將會是oom killer

接下來的知識有些燒腦,但有些名詞,可能是你已經聽過多次的了。

操作系統視角

我們來解釋一下上圖,第一部分是邏輯內存和物理內存的關係;第二部分是top命令展示的一個結果,詳細的列出了每一個進程的內存使用情況;第三部分是free命令展示的結果,它的關係比較亂,所以給加上了箭頭來作說明。

  • 學過計算機組成結構的都知道,程序編譯後的地址是邏輯內存,需要經過翻譯才能映射到物理內存。這個管翻譯的硬體,就叫MMUTLB就是存放這些映射的小緩存。內存特別大的時候,會涉及到hugepage,在某些時候,是進行性能優化的殺手鐧,比如優化redis (THP,注意理解透徹前不要妄動)
  • 物理內存的可用空間是有限的,所以邏輯內存映射一部分地址到硬碟上,以便獲取更大的物理內存地址,這就是swap分區。swap是很多性能場景的萬惡之源,建議禁用
  • top展示的欄位,RES才是真正的物理內存佔用(不包括swap,ps命令里叫RSS)。在java中,代表了堆內+堆外內存的總和。而VIRT、SHR等,幾乎沒有判斷價值(某些場景除外)
  • 系統的可用內存,包括:free + buffers + cached,因為後兩者可以自動釋放。但不要迷信,有很大一部分,你是釋放不了的
  • slab區,是內核的緩存文件句柄等信息等的特殊區域,slabtop命令可以看到具體使用

更詳細的,從/proc/meminfo文件中可以看到具體的邏輯內存塊的大小。有多達40項的內存信息,這些信息都可以通過/proc一些文件的遍歷獲取,本文只挑重點說明。

[xjj@localhost ~]$ cat /proc/meminfo

MemTotal: 3881692 kB

MemFree: 249248 kB

MemAvailable: 1510048 kB

Buffers: 92384 kB

Cached: 1340716 kB

40+ more ...

oom-killer

以下問題已經不止一個小夥伴問了:我的java進程沒了,什麼都沒留下,就像個屁一樣蒸發不見了

why?是因為對象太多了么?

執行dmesg命令,大概率會看到你的進程崩潰信息躺屍在那裡。

為了能看到發生的時間,我們習慣性加上參數T

dmesg -T

由於linux系統採用的是虛擬內存,進程的代碼的使用都會消耗內存,但是申請出來的內存,只要沒真正access過,是不算的,因為沒有真正為之分配物理頁面。

第一層防護牆就是swap;當swap也用的差不多了,會嘗試釋放cache;當這兩者資源都耗盡,殺手就出現了。oom killer會在系統內存耗盡的情況下跳出來,選擇性的幹掉一些進程以求釋放一點內存。2.4內核殺新進程;2.6殺用的最多的那個。所以,買內存吧。

這個oom和jvm的oom可不是一個概念。順便,瞧一下我們的JVM堆在什麼位置。

例子

jvm內存溢出排查

應用程序發布後,jvm持續增長。使用jstat命令,可以看到old區一直在增長。

jstat -gcutil 28266 1000

在jvm參數中,加入-XX:+HeapDumpOnOutOfMemoryError,在jvm oom的時候,生成hprof快照。然後,使用Jprofile、VisualVM、Mat等打開dump文件進行分析。

你要是個急性子,可以使用jmap立馬dump一份

jmap -heap:format=b pid

最終發現,有一個全局的Cache對象,不是guava的,也不是commons包的,是一個簡單的ConcurrentHashMap,結果越積累越多,最終導致溢出。

溢出的情況也有多種區別,這裡總結如下:

| 關鍵字 | 原因 |

| —- | —- |

|Java.lang.OutOfMemoryError: Java heap space | 堆內存不夠了,或者存在內存溢出 |

|java.lang.OutOfMemoryError: PermGen space | Perm區不夠了,可能使用了大量動態載入的類,比如cglib |

| java.lang.OutOfMemoryError: Direct buffer memory| 堆外內存、操作系統沒內存了,比較嚴重的情況 |

|java.lang.StackOverflowError |調用或者遞歸層次太深,修正即可 |

|java.lang.OutOfMemoryError: unable to create new native thread| 無法創建線程,操作系統內存沒有了,一定要預留一部分給操作系統,不要都給jvm|

| java.lang.OutOfMemoryError: Out of swap space| 同樣沒有內存資源了,swap都用光了|

jvm程序內存問題,除了真正的內存泄漏,大多數都是由於太貪心引起的。一個4GB的內存,有同學就把jvm設置成了3840M,只給操作系統256M,不死才怪。

另外一個問題就是swap了,當你的應用真正的高並發了,swap絕對能讓你體驗到它魔鬼性的一面:進程倒是死不了了,但GC時間長的無法忍受。

我的ES性能低

業務方的ES集群宿主機是32GB的內存,隨著數據量和訪問量增加,決定對其進行擴容=>內存改成了64GB。

內存升級後,發現ES的性能沒什麼變化,某些時候,反而更低了。

通過查看配置,發現有兩個問題引起。

一、64GB的機器分配給jvm的有60G,預留給文件緩存的只有4GB,造成了文件緩存和硬碟的頻繁交換,比較低效。

二、JVM大小超過了32GB,內存對象的指針無法啟用壓縮,造成了大量的內存浪費。由於ES的對象特別多,所以留給真正緩存對象內容的內存反而減少了。

解決方式:給jvm的內存30GB即可。

其他

基本上了解了內存模型,上手幾次內存溢出排查,內存問題就算掌握了。但還有更多,這條知識系統可以深挖下去。

JMM

還是拿java來說。java中有一個經典的內存模型,一般面試到volitile關鍵字的時候,都會問到。其根本原因,就是由於線程引起的。

當兩個線程同時訪問一個變數的時候,就需要加所謂的鎖了。由於鎖有讀寫,所以java的同步方式非常多樣。wait,notify、lock、cas、volitile、synchronized等,我們僅放上volitile的讀可見性圖作下示例。

線程對共享變數會拷貝一份到工作區。線程1修改了變數以後,其他線程讀這個變數的時候,都從主存里刷新一份,此所謂讀可見。

JMM問題是純粹的內存問題,也是高級java必備的知識點。

CacheLine & False Sharing

是的,內存的工藝製造還是跟不上CPU的速度,於是聰明的硬體工程師們,就又給加了一個緩存(哦不,是多個)。而Cache Line為CPU Cache中的最小緩存單位。

這個緩存是每個核的,而且大小固定。如果存在這樣的場景,有多個線程操作不同的成員變數,但是相同的緩存行,這個時候會發生什麼?。沒錯,偽共享(False Sharing)問題就發生了!

偽共享也是高級java的必備技能(雖然幾乎用不到),趕緊去探索吧。

HugePage

回頭看我們最長的那副圖,上面有一個TLB,這個東西速度雖然高,但容量也是有限的。當訪問頻繁的時候,它會成為瓶頸。

TLB是存放Virtual Address和Physical Address的映射的。如圖,把映射闊上一些,甚至闊上幾百上千倍,TLB就能容納更多地址了。像這種將Page Size加大的技術就是Huge Page。

HugePage有一些副作用,比如競爭加劇(比如redis: redis.io/topics/latency )。但在大內存的現代,開啟後會一定程度上增加性能(比如oracle: docs.oracle.com/cd/E118 )。

Numa

本來想將Numa放在cpu篇,結果發現numa改的其實是內存控制器。這個東西,將內存分段,分別」綁定」在不同的CPU上。也就是說,你的某核CPU,訪問一部分內存速度賊快,但訪問另外一些內存,就慢一些。

所以,Linux識別到NUMA架構後,默認的內存分配方案就是:優先嘗試在請求線程當前所處的CPU的內存上分配空間。如果綁定的內存不足,先去釋放綁定的內存。

以下命令可以看到當前是否是NUMA架構的硬體。

numactl --hardware

NUMA也是由於內存速度跟不上給加的折衷方案。Swap一些難搞的問題,大多是由於NUMA引起的。

總結

本文的其他,是給想更深入理解內存結構的同學準備的提綱。Linux內存牽扯的東西實在太多,各種緩衝區就是魔術。如果你遇到了難以理解的現象,費了九牛二虎之力才找到原因,不要感到奇怪。對發生的這一切,我深表同情,並深切的渴望通用量子計算機的到來。

那麼問題來了,內存尚且如此,磁碟呢?


推薦閱讀:

基於梯度的優化(V):動力系統,控制論
個人電腦優化推薦
超級公開課NVIDIA專場特別企劃!面向典型地球科學應用的GPU優化方案|報名
Goldstein準則優化演算法(julia實現)
9 月產品更新 | 增設 Markdown 語法支持開關 更多功能優化

TAG:Linux | 優化 | 內存(RAM) |