如何使用Eclipse內存分析工具定位內存泄露
{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文以我司生產環境Java應用內存泄露爲案例進行分析,講解如何使用Eclipse的MAT分析定位問題","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"11月10號晚上8點收到報警郵件,一看是OOM","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打開公司監控系統查看應用各項指標發現JVM中老年代在持續增長(從上次發佈10月30號到11月10號的12天內一直在增長, 存在內存泄露跡象)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e7/e701977a8e0626542d69b400072f0ffe.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中可以看出, 從10月30號發佈到11月10號oom期間11天老年代一直在緩慢上漲, 雖然有下降, 但整體趨勢是上升的,平均每天泄露約50M內存, 說明每次都無法完全釋放乾淨","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲生產環境的JVM添加了 -XX:+HeapDumpOnOutOfMemoryError 參數,該配置會把dump文件的快照保存下來供後續分析排查問題,也可以使用jmap或jcmd等jvm命令進行dump:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"jmap -dump:format=b,file=文件名 [pid]\njcmd pid GC.heap_dump 文件路徑\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"分析內存泄露","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內存泄露和內存溢出的區別:內存泄露從老年代的增長情況看是緩慢上升的, 最終達到老年代上限纔會導致溢出,有些內存泄露可能需要很長的時間發生, 所以說內存泄露更隱蔽, 不像內存溢出那樣容易暴露(內存溢出直接拋出OOM), 而且內存長時間得不到釋放會導致服務性能越來越差、gc時間變長、響應變慢:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/45f01082f50ad02171a9785a40d00a5d.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中可以看出在12天裏每天大概泄露(增長) 50M 左右, 這種情況下定位泄露原因需要多次dump採集樣本, 然後和上次的比較分析, 即需要多個dump文件進行比較分析才能精確定位問題。 否則很難看出具體泄露的點, 加上dump文件中大部分是正常的內存使用, 會干擾問題的定位, 增加排查難度。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以當時的做法是每天固定時間dump一次, 採集足夠多的樣本, 如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b7/b72f6cf739e9f27da68e8d2f5cd2091d.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外測試環境不好重現的主要原因是不清楚是哪個接口調用引起的, 這個Java服務有多個暴露的api, 而且測試環境不方便壓測,壓測量大了, 底層接口熔斷, 壓測量小看不出泄露跡象, 所以得從dump分析入手, 找到問題所在再去測試環境驗證。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏使用Eclipse的memory analysis tool(MAT)工具進行分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把下載到本地的多個dump文件用mat依次打開(“File → Open Heap Dump”), 如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c5/c5bd7cf620c7b2821d2a1a185c978e63.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如我們要分析這3個dump文件(當然你也可以分析更多個, 這樣會更精準), 打開後, 使用compare basket功能找出內存泄露的差異點:","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"使用 compare basket 功能分析內存泄露","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1> 菜單欄 window → compare basket ,打開比較窗口(如果最下面一欄已經有compare basket則這步不需要),如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4c/4cab29676d5c466499624487cb4d4b69.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2> 依次打開3個dump的dashboard面板, 在下方的 Actions一欄點擊\"histogram\"或\"dominator tree\"生成對應的直方圖或支配樹列表,如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7e/7e70e15b4a470e3ad61318804d6b9f2a.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直方圖或支配樹都可以列出堆中存活的所有對象,但二者的維度不同, 直方圖按照類型統計, 支配樹是以對象維度統計。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你對項目代碼比較熟悉, 通過直方圖定位內存泄露會更快,因爲它是按照類型全部平鋪開的,如果這個項目不是你負責的, 建議使用支配樹的方式, 因爲支配樹包含了對象之間的引用關係(支配樹視圖可以展開查看內部引用層級)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3> 我們以支配樹做比對, 在最下面一欄的\"Navigation History (window → navigation history)\"裏(直方圖類似)找到在第2步打開的支配樹dominator tree圖標, 右鍵添加到compare basket, 如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/64/64c1387238d4254875da9a6c44d60a77.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4> 重複上面的2, 3步驟依次把其他的dump文件添加到\"compare basket\"欄, 然後點擊右上角的紅色感嘆號, 生成比較結果,如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/83/839e3ad7316dbac4771e57fd386c811b.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(注意比較的dump文件的順序,時間最早的在上面,可以通過右上角的上箭頭↑和下箭頭↓調整順序)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生成的比對結果如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5e/5e85efdaa5c3f8efe44da024a3867602.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Shallow Heap一列後面的序號 #0, #1, #2 分別對應:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個dump文件佔用的shallow size, 第二個dump文件佔用的shallow size , 第三個dump文件佔用的shallow size","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Retained Heap #0, Retained Heap #1, Retained Heap #2 這3列分別對應:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個dump文件佔用的retained size, 第二個dump文件佔用的retained size , 第三個dump文件佔用的retained size","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過Retained Heap的變化趨勢可以看出:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"紅框 圈出的是內存連續增長的對象, 可以通過右邊紅框的retained heap看出內存變大的趨勢","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"綠框 圈出的是沒有變化的對象(至少在這3次比較中沒有變化)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藍框 圈出的是內存佔用下降的對象","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般我們主要關注紅框標出的對象, 因爲這部分發生內存泄露的嫌疑最大","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏先區分兩個概念:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Shallow Size","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對象自身佔用的內存大小,不包括它引用的對象。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對非數組類型的對象,它的大小就是對象與它所有的成員變量大小的總和。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對數組類型的對象,它的大小是數組元素對象的大小總和。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Retained Size","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Retained Size=當前對象大小+當前對象可直接或間接引用到的對象的大小總和。(間接引用的含義:A->B->C, C就是間接引用)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Retained Size就是當前對象被GC後,從Heap上總共能釋放掉的內存。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲這裏我們比較的是支配樹, 所以按照retained heap倒序排列, 從左到右依次爲: retained heap #0 → retained heap #1 → retained heap #2(以最後一個retained heap #2 倒序, 因爲這個是最後一次dump的內存快照, 這樣可以看出內存泄露的增長趨勢)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"定位內存泄露","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基於上一步得出的比較結果, 可以看出\"","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"org.apache.tomcat.util.threads.TaskThread http-nio-8080-exec-*","attrs":{}}],"attrs":{}},{"type":"text","text":"\" 有內存泄露的嫌疑, 查看它的引用關係:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/28/28be28f2b346b07a5f84a3f49d2d330b.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點擊\"with outgoing references\"後逐層展開第一個對象內部的引用關係(以Retained Heap倒序,主要是看retained size排在前面的對象), 如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c7/c7c920c2e68d6b4c264621cd5da9c1dd.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到TaskThead內部有一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"threadLocal","attrs":{}}],"attrs":{}},{"type":"text","text":", threadLocal內部有一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"concurrentHashMap","attrs":{}}],"attrs":{}},{"type":"text","text":",這個map裏存的是我們的日誌相關對象\"","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"com.*.framework.log.FieldAppendedValue","attrs":{}}],"attrs":{}},{"type":"text","text":"\",從下面幾個map裏的key可以確定是我們記錄到日誌系統(ElasticSearch)的對象, 這些日誌對象主要記錄調用接口的請求報文、響應報文、SOA接口名稱等信息,如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/40/405d810db4d307d2fd0e3ed09ff87082.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但爲什麼日誌對象會佔用這麼多內存?而且這裏看到的只是其中一個taskThread裏,繼續展開RESPONSE_CONTENT的val對象FieldAppendedValue內部引用, 如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ad/ad9376c84b0500d09e42b3d7e0f1a44f.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發現","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FieldAppendedValue","attrs":{}}],"attrs":{}},{"type":"text","text":"內部維護了一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CopyOnWriteArrayList","attrs":{}}],"attrs":{}},{"type":"text","text":"對象, 這個list裏竟然存放了10674個值,正常來講不可能一次接口請求會有這麼多的日誌對象, 而且接口請求完記錄到ES後, 這部分內存就應該釋放了纔對。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查看CopyOnWriteArrayList內部存儲的內容,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/76/76e7b49226891e6d23e095a339e1c40d.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨便打開10675箇中的幾個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FieldAppendedValue","attrs":{}}],"attrs":{}},{"type":"text","text":", 發現內部存放的都是同一個接口的請求響應報文,如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4f/4f8254737bfe5918046f07c14a63a917.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以右鍵copy→ value 把值複製出來查看, 接口報文如下:(響應報文)","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"{\n \"ResponseStatus\": {\n \"Timestamp\": \"/Date(1605583909438+0800)/\",\n \"Ack\": \"Success\",\n \"Errors\": [],\n \"Build\": null,\n \"Version\": null,\n \"Extension\": []\n },\n \"downloadUrl\": \"https://ii066.cn/hFGBEW\"\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面那張","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"concurrentHashMap","attrs":{}}],"attrs":{}},{"type":"text","text":"截圖(key : SOA_METHOD_NAME) 可知這個接口名是: getDownloadLink, 也就是說list裏10675個日誌對象存的都是\"getDownloadLink\"這個接口的報文。而且這只是其中一個TaskThead內部情況, 加上全部20個對象, 20 * 10675 大概是213500個接口報文,如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d0/d07e913d143e295e67bde556f6202697.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個接口是什麼鬼?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/59/593c9e70ee6eddbcdff30d47fb598120.png","alt":"image.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"代碼分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查看代碼得知這個接口並沒什麼幺蛾子,只是當時的開發同學在調用這個底層接口時新接入了我們部門封裝的SOA組件公共類:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AbstractSimpleHandler.java","attrs":{}}],"attrs":{}},{"type":"text","text":"(這個公共類主要是通過模板方法在調用接口時記錄報文日誌埋點、超時時間設置、mock等功能)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這次出現OOM的這個Java項目之前調用soa接口是自己實現了一套公共方法(早於框架之前實現), 也就是說只有這一個接口使用了新的公共類","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AbstractSimpleHandler","attrs":{}}],"attrs":{}},{"type":"text","text":",其他的接口調用方式還是原來的方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新的工具類","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AbstractSimpleHandler","attrs":{}}],"attrs":{}},{"type":"text","text":"記錄接口報文的代碼是通過調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ELKLogUtils.write()","attrs":{}}],"attrs":{}},{"type":"text","text":"實現的, 這個方法的內部大致邏輯如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"Object value = HttpContext.get(BEHAVIOR_LOG);\n if (value == null) {\n value = new ConcurrentHashMap<>();\n HttpContext.add(BEHAVIOR_LOG, value);\n }\n\n\nHttpContext內部維護的是一個ThreadLocal:\n\n\n\npublic class HttpContext {\n\n private static final int CONTEXT_DEFAULT_SIZE = 1 << 6;\n\n private static final ThreadLocal
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.