內存問題,腦瓜疼腦瓜疼。腦瓜疼的意思,就是腦袋運算空間太小,撐的疼。本篇是《荒島餘生》系列第三篇,讓人腦瓜疼的內存篇。其餘參見:
Linux之《荒島餘生》(一)準備篇
Linux之《荒島餘生》(二)CPU篇
小公司請求量小,但喜歡濫用內存,開一堆線程,大把大把往jvm塞對象,最終問題是內存溢出。
大公司並發大,但喜歡強調HA,所以通常保留swap,最終問題是服務卡頓。
而喜歡用全局集合變數的某些同仁,把java代碼當c寫,對象塞進去但忘了銷毀,最終問題是內存泄漏。
如何避免?
合理參數、優雅代碼、禁用swap,三管齊下,
trouble shooter。
一個陽光明媚的下午,一條報警簡訊彈了出來。老王微微一笑,是cpu問題,idle瞬時值,大概是某批請求比較大引起的峰值問題。老王每天都會收到這樣的簡訊,這樣的一個小峰值,在數千台伺服器中,不過是滄海一栗,繼續喝茶就是了。
但,這次不一樣。幾分鐘之後,幾百個服務的超時報警鋪天蓋地到來。事後老王算了一下,大概千分之零點幾的服務超時了,不過這已經很恐怖了。
事態升級,恐怕沒時間喝茶了。
大面積報警,應該是全局問題,是網路卡頓?還是資料庫抽風?老王挑了一台最近報警的伺服器,輪流監控了各種狀態,總結如下:
全局性的東西不太多,網關、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~,我們先上一點命令。
#根據使用量排序查看RES
top -> shift + m
#查看進程使用的物理內存
ps -p 75 -o rss,vsz
#顯示內存的使用情況
free -h
#使用sar查看內存信息
sar -r
#顯示內存每個區的詳情
cat /proc/meminfo
#查看slab區使用情況
slabtop
通常,通過查看物理內存的佔用,你發現不了多少問題,頂多發現那個進程佔用內存高(比如vim等旁路應用)。meminfo和slabtop對系統的全局判斷幫助很大,但掌握這兩點坡度陡峭。
#查看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罪與罰》這篇文章看下,千萬不要更暈哦。
# 查看系統級別的故障和問題
dmesg
# 統計實例最多的類前十位
jmap -histo pid | sort -n -r -k 2 | head -10
# 統計容量前十的類
jmap -histo pid | sort -n -r -k 3 | head -10
以上命令是看堆內的,能夠找到一些濫用集合的問題。堆外內存,依然推薦
《Java堆外內存排查小結》
# 釋放內存
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命令展示的結果,它的關係比較亂,所以給加上了箭頭來作說明。
MMU
TLB
hugepage
swap
top
RES
RSS
free
buffers
cached
更詳細的,從/proc/meminfo文件中可以看到具體的邏輯內存塊的大小。有多達40項的內存信息,這些信息都可以通過/proc一些文件的遍歷獲取,本文只挑重點說明。
/proc/meminfo
[xjj@localhost ~]$ cat /proc/meminfo
MemTotal: 3881692 kB
MemFree: 249248 kB
MemAvailable: 1510048 kB
Buffers: 92384 kB
Cached: 1340716 kB
40+ more ...
以下問題已經不止一個小夥伴問了:我的java進程沒了,什麼都沒留下,就像個屁一樣蒸發不見了
why?是因為對象太多了么?
執行dmesg命令,大概率會看到你的進程崩潰信息躺屍在那裡。
為了能看到發生的時間,我們習慣性加上參數T
dmesg -T
由於linux系統採用的是虛擬內存,進程的代碼,庫,堆和棧的使用都會消耗內存,但是申請出來的內存,只要沒真正access過,是不算的,因為沒有真正為之分配物理頁面。
代碼
庫
堆
棧
第一層防護牆就是swap;當swap也用的差不多了,會嘗試釋放cache;當這兩者資源都耗盡,殺手就出現了。oom killer會在系統內存耗盡的情況下跳出來,選擇性的幹掉一些進程以求釋放一點內存。2.4內核殺新進程;2.6殺用的最多的那個。所以,買內存吧。
這個oom和jvm的oom可不是一個概念。順便,瞧一下我們的JVM堆在什麼位置。
應用程序發布後,jvm持續增長。使用jstat命令,可以看到old區一直在增長。
jstat -gcutil 28266 1000
在jvm參數中,加入-XX:+HeapDumpOnOutOfMemoryError,在jvm oom的時候,生成hprof快照。然後,使用Jprofile、VisualVM、Mat等打開dump文件進行分析。
-XX:+HeapDumpOnOutOfMemoryError
你要是個急性子,可以使用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集群宿主機是32GB的內存,隨著數據量和訪問量增加,決定對其進行擴容=>內存改成了64GB。
內存升級後,發現ES的性能沒什麼變化,某些時候,反而更低了。
通過查看配置,發現有兩個問題引起。
一、64GB的機器分配給jvm的有60G,預留給文件緩存的只有4GB,造成了文件緩存和硬碟的頻繁交換,比較低效。
二、JVM大小超過了32GB,內存對象的指針無法啟用壓縮,造成了大量的內存浪費。由於ES的對象特別多,所以留給真正緩存對象內容的內存反而減少了。
解決方式:給jvm的內存30GB即可。
基本上了解了內存模型,上手幾次內存溢出排查,內存問題就算掌握了。但還有更多,這條知識系統可以深挖下去。
還是拿java來說。java中有一個經典的內存模型,一般面試到volitile關鍵字的時候,都會問到。其根本原因,就是由於線程引起的。
當兩個線程同時訪問一個變數的時候,就需要加所謂的鎖了。由於鎖有讀寫,所以java的同步方式非常多樣。wait,notify、lock、cas、volitile、synchronized等,我們僅放上volitile的讀可見性圖作下示例。
線程對共享變數會拷貝一份到工作區。線程1修改了變數以後,其他線程讀這個變數的時候,都從主存里刷新一份,此所謂讀可見。
JMM問題是純粹的內存問題,也是高級java必備的知識點。
是的,內存的工藝製造還是跟不上CPU的速度,於是聰明的硬體工程師們,就又給加了一個緩存(哦不,是多個)。而Cache Line為CPU Cache中的最小緩存單位。
這個緩存是每個核的,而且大小固定。如果存在這樣的場景,有多個線程操作不同的成員變數,但是相同的緩存行,這個時候會發生什麼?。沒錯,偽共享(False Sharing)問題就發生了!
偽共享也是高級java的必備技能(雖然幾乎用不到),趕緊去探索吧。
回頭看我們最長的那副圖,上面有一個TLB,這個東西速度雖然高,但容量也是有限的。當訪問頻繁的時候,它會成為瓶頸。
TLB是存放Virtual Address和Physical Address的映射的。如圖,把映射闊上一些,甚至闊上幾百上千倍,TLB就能容納更多地址了。像這種將Page Size加大的技術就是Huge Page。
HugePage有一些副作用,比如競爭加劇(比如redis: https://redis.io/topics/latency )。但在大內存的現代,開啟後會一定程度上增加性能(比如oracle: https://docs.oracle.com/cd/E11882_01/server.112/e10839/appi_vlm.htm )。
本來想將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) |