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

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

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

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

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

大公司併發大,但喜歡強調HA,因此一般保留swap,最終問題是服務卡頓。數據庫

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

如何避免? 合理參數、優雅代碼、禁用swap,三管齊下, trouble shooter。緩存

從一個故事開始

老王的疑問

一個陽光明媚的下午,一條報警短信彈了出來。老王微微一笑,是cpu問題,idle瞬時值,大概是某批請求比較大引發的峯值問題。老王天天都會收到這樣的短信,這樣的一個小峯值,在數千臺服務器中,不過是滄海一慄,繼續喝茶就是了。性能優化

但,此次不同。幾分鐘以後,幾百個服務的超時報警鋪天蓋地到來。過後老王算了一下,大概千分之零點幾的服務超時了,不過這已經很恐怖了。 事態升級,恐怕沒時間喝茶了。bash

大面積報警,應該是全局問題,是網絡卡頓?仍是數據庫抽風?老王挑了一臺最近報警的服務器,輪流監控了各類狀態,總結以下:服務器

  • 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的性能沒什麼變化,某些時候,反而更低了。

經過查看配置,發現有兩個問題引發。 1、64GB的機器分配給jvm的有60G,預留給文件緩存的只有4GB,形成了文件緩存和硬盤的頻繁交換,比較低效。 2、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/late… )。但在大內存的現代,開啓後會必定程度上增長性能(好比oracle: docs.oracle.com/cd/E11882_0… )。

Numa

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

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

如下命令能夠看到當前是不是NUMA架構的硬件。

numactl --hardware
複製代碼

NUMA也是因爲內存速度跟不上給加的折衷方案。Swap一些難搞的問題,大可能是因爲NUMA引發的。

總結

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

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

相關文章
相關標籤/搜索