https://www.cnblogs.com/zhenglisai/p/14751677.html
現象
隨着程序運行,內存占用率越來越高,直到觸發linux的OOM,程序被殺死。
分析工具
運行環境:.net core 3.1(微軟的分析工具要求最低3.0,無法分析2.1的core程序,需要先改為core 3.1才能分析)
linux:ubuntu 18
分析工具:dotnet-counters, dotnet-dump
工具的安裝見:https://docs.microsoft.com/zh-cn/dotnet/core/diagnostics/dotnet-counters
分析過程
1,獲取要分析進程的pid
使用top或者ps等等工具,獲取程序的pid
對於docker環境,如果沒有安裝top命令,可以使用如下安裝
apt-get install procps
2,查看內存使用情況(我這里pid為13156)
dotnet-counters monitor -p 13156
從結果來看,GC中的Gen2占用了較多的內存,理論上,不應該有很多的Gen2,我們需要分析一下Gen2里面到底是什么?
Gen0,Gen1,Gen2以及LOH的區別,以及.net core內存管理機制,見:
https://docs.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-5.0
3,獲取進程的dump文件
dotnet-dump collect -p 13156
說明:要使用這條命令獲取dump,如果在docker中,需要提供docker的--private參數,如果是在AWS的ECS中使用的Fargate模式運行,則不支持此參數。需要在EC2上運行。
此命令會在當前目錄生成一個dump文件
4,分析dump文件
dotnet-dump analyze core_20210510_054712
# 分析gen2中的內容,每個命令的參數以及和含義,可以使用help查看
dg gen2
從結果來看,有很多string類型的數據在gen2中,以及mysql的一些數據,我們打開看看具體是什么內容
看輸出,有很多一樣的內容,我們隨便打開一個看看
可以看到內容就是數據庫的返回數據
同樣的方法,我們看看哪些string里面都是什么
有非常多的對象,我們也是隨便打開一個看看內容
看着像是web的打印
總結
獲取dump文件
dotnet-dump collect <pid>
分析dump文件
dotnet-analyze xxxxx
獲取gen2或者其他的內存數據
dg gen2 | gen1 | gen1 | genloh
查看內存數據類型
dumpheap -mt xxxxxx
查看內存數據的具體內容
do xxxxxx
通過具體內容,配合開發人員定位代碼問題
-------------------------------------------
2021-5-11 更新
再說明一下我們這邊的運行環境,以及代碼中用到的相關服務
我們正式程序運行在AWS基於Fargate的ECS上,容器配置為0.5vCPU, 512MB內存,.net core程序版本為2.1,數據庫查詢使用sqlsugar,aws服務用到了Dynamodb和SNS
問題就是程序運行大約1天就出現OOM,導致容器重啟。
下面是我們排查問題的過程
Round 1:將core從2.1升級到3.1
原因:根據微軟的說法,3.0以后優化了core在linux下以及容器中的性能,降低了內存占用,詳見下面的連接。
https://devblogs.microsoft.com/dotnet/using-net-and-docker-together-dockercon-2019-update/
說明:2.1升級到3.1還是有很多地方需要修改的,微軟這方面做的就不夠好,但是3.1升級5.0據說改動不大,這里要感謝我們的開發同學積極配合修改了代碼。
結論:內存增長速度為原來的一半。
內存增長速度變慢了,但是仍然在增長,沒有解決根本問題。
Round 2:調整GC模式,從默認的Server GC調整為WorkStation GC
原因:WorkStaionGC會使用更少的內存,回收的更頻繁,但是性能可能會稍差一下,根據微軟的說法,在docker環境中還是推薦使用WorkStaion GC模式,兩種GC的對比,以及推薦詳見下文:
https://docs.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-3.1#workstation-gc-vs-server-gc
結論:內存增長速度又降低了一半,但是仍然在增長,還是沒有解決根本問題
Round 3:Gen2中的內存到底是什么?
原因:既然內存不停的在漲,而且通過分析工具可以看到主要是GC中的Gen2部分在增長,按照微軟的說法內存中的垃圾數據根據時間的長短,依次存入Gen0, Gen1, Gen2。而且Gen1和Gen2是由core進行垃圾回收的,不需要我們干預,那么Gen2中的內容到底是什么?為什么一直沒有被回收?
這一部分就是上面的文章內容,通過以上方法我們已經知道Gen2中的內存主要是變量、數據庫查詢結果、console控制台打印。奇了怪了,為什么這些東西會在內存里不釋放?
關於core的內存管理以及GC原理,見下面的文章
https://docs.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-3.1
結論:找到了內存中的數據,但是不解這些數據為什么沒有被回收
Round 4:關閉這些打印看看
原因:既然Gen2中存在大量的控制台打印,那么我如果關閉控制台打印呢?是不是就沒有這部分的內存占用了
結論:沒啥作用,內存仍然在不停的增長
Round 5:是不是數據庫工具有問題?
原因:既然內存中有大量的數據庫查詢結果,那么是不是因為我們用了sqlsugar導致的?sqlsugar本身有什么緩存的機制?
我們查了sqlsugar的官方,sqlsugar確實支持二級緩存,但是我們沒有用上,詳見官方文檔:
https://www.donet5.com/home/Doc?typeId=1214
為此,我們直接刪除了sqlsugar部分代碼,不查詢數據庫了,直接寫死在代碼里返回。然后開始跑壓力測試(這時候接口已經沒有業務邏輯了)
結論:沒啥用,內存還在增長
Round 6:放大招了,寫一個空接口,沒有任何邏輯,直接返回固定字符串
原因:做減法不行,我們開始做加法,從0開始寫接口,一點點功能添加,看看到底添加到哪一步的時候,導致內存增加
結論:老實了,內存終於不增長了(准確的說增長的非常緩慢,一晚上增加了0.2%的內存)
至少說明core本身在docker環境下運行,沒有明顯的內存泄露問題,問題應該出在代碼邏輯上
###############################
今天到此為止,等我們后續的嘗試出了結果再更新,希望我們的測試過程對大家有些參考。