乾貨分享:淺談記憶體洩露
作者:孤獨煙 來源:微信公眾號(打雜的ZRJ)
原文連結:https://mp.weixin.qq.com/s/TEeuqi3PXfpj0zvDKrX_vQ 孤獨煙
前言
這個話題已經是老生常談了,之所以又被我拎出來,是因為博主隔壁的一個童鞋最近寫了一篇叫做《ThreadLocal記憶體洩露》的文章,我就不上鍊接了,因為寫的實在是。。
(省略一萬字)
重點是寫完後,還被我問懵了。出於人道主義關懷,博主很不要臉的再寫一篇。
正文
定義
首先,我們要先談一下定義,因為一堆人搞不懂記憶體溢位和記憶體洩露的區別。
記憶體溢位(OutOfMemory):
你只有十塊錢,我卻找你要了一百塊。對不起啊,我沒有這麼多錢。
(給不起)
記憶體洩露(MemoryLeak):
你有十塊錢,我找你要一塊。但是無恥的博主,不把錢還你了。
(沒退還)
關係:
多次的記憶體洩露,會導致記憶體溢位。(博主不要臉的找你多要幾次錢,你就沒錢了,就是這個道理。)
危害
ok,大家在專案中有沒遇到過java程式越來越卡的情況。
因為記憶體洩露,會導致頻繁的
Full GC
,而
Full GC
又會造成程式停頓,最後Crash了。因此,你會感覺到你的程式越來越卡,越來越卡,然後你就被產品經理鄙視了。順便提一下,我們之所以JVM調優,就是為了減少
Full GC
的出現。
我記得,我曾經有一次,就遇到專案剛上線的時候好好的。結果隨著時間的堆積,報了
OutOfMemoryError: PermGen space
。
說到這個
PermGen space
,突然間,一陣洪荒之力,從博主體內噴湧而出,一定要介紹一下這個方法區,不過點到為止,畢竟這不是在講《jvm從入門到放棄》。
方法區
:出自java虛擬機器規範, 可供
各條執行緒共享
的
執行時記憶體區域
。它儲存了
每一個類的結構資訊
,例如執行時常量池(
Runtime Constant Pool
)、欄位和方法資料、建構函式和普通方法的位元組碼內容。
上面講的是規範,在不同虛擬機器裡頭實現是不一樣的,最典型的就是
永久代(PermGen space)
和
元空間(Metaspace)
。
jdk1.8以前: 實現方法區的叫永久代。因為在很久遠以前,java覺得類幾乎是 靜態的 ,並且很少被解除安裝和回收,所以給了一個 永久代 的雅稱。 因此 ,如果你在專案中,發現堆和永久代一直在不斷增長,沒有下降趨勢,回收的速度根本趕不上增長的速度,不用說了,這種情況基本可以確定是記憶體洩露。
jdk1.8以後:
實現方法區的叫元空間。Java覺得對永久代進行調優是很困難的。永久代中的後設資料可能會隨著每一次
Full GC
發生而進行移動。並且為永久代設定空間大小也是很難確定的。
因此
,java決定將類的後設資料分配在本地記憶體中,元空間的最大可分配空間就是系統可用記憶體空間。這樣,我們就避開了設定永久代大小的問題。
但是
,這種情況下,一旦發生記憶體洩露,會佔用你的大量本地記憶體。如果你發現,你的專案中本地記憶體佔用率異常高。嗯,這就是記憶體洩露了。
如何排查
(1)透過
jps
查詢java程式id。
(2)透過
top -p [pid]
發現記憶體佔用達到了最大值
(3)
jstat -gccause pid 20000
每隔20秒輸出
Full GC
結果
(4)發現
Full GC
次數太多,基本就是記憶體洩露了。生成
dump
檔案,藉助工具分析是哪個物件太多了。基本能定位到問題在哪。
例項
在stackoverflow上,有一個問題,如下所示
I just had an interview, and I was asked to create a memory leak with Java. Needless to say I felt pretty dumb having no clue on how to even start creating one.
大致就是,因為面試需要手寫一段記憶體洩露的程式,然後提問的人突然懵逼了,於是很多大佬紛紛給出回答。
案例一
此例子出自《演算法》(第四版)一書,我簡化了一下
class stack{ Object data[1000]; int top = 0; public void push(Object o){ data[top++] = o; } public Object pop(Object o){ return data[--top]; } }
當資料從棧裡面彈出來之後,data陣列還一直保留著指向元素的指標。那麼就算你把棧pop空了,這些元素佔的記憶體也不會被回收的。
解決方案就是
public Object pop(Object o){ Object result = data[--top]; data[top] = null; return result; }
案例二
這個其實是一堆例子,這些例子造成記憶體洩露的原因都是類似的,就是
不關閉流
,具體的,可以是檔案流,socket流,資料庫連線流,等等
具體如下,沒關檔案流
try { BufferedReader br = new BufferedReader(new FileReader(inputFile)); ... ... } catch (Exception e) { e.printStacktrace(); }
再比如,沒關閉連線
try { Connection conn = ConnectionFactory.getConnection(); ... ... } catch (Exception e) { e.printStacktrace(); }
解決方案就是。。。嗯,大家應該都會。。你敢說你不會調
close()
方法。
案例三
講這個例子前,大家對
ThreadLocal
在
Tomcat
中引起記憶體洩露有了解麼。不過,我要說一下,這個洩露問題,和ThreadLocal本身關係不大,我看了一下官網給的例子,基本都是屬於使用不當引起的。
在Tomcat的官網上,記錄了這個問題。地址是:
不過,官網的這個例子,可能不好理解,我們略作改動。
public class HelloServlet extends HttpServlet{ private static final long serialVersionUID = 1L; static class LocalVariable { private Long[] a = new Long[1024 * 1024 * 100]; } final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>(); @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { localVariable.set(new LocalVariable()); } }
再來看下conf下sever.xml配置
<!--The connectors can use a shared executor, you can define one or more named thread pools--> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/>
執行緒池最大執行緒為150個,最小執行緒為4個
Tomcat中Connector元件負責接受並處理請求,每來一個請求,就會去執行緒池中取一個執行緒。
在訪問該servlet時,
ThreadLocal
變數裡面被新增了
new LocalVariable()
例項,但是沒有被
remove
,這樣該變數就隨著執行緒回到了執行緒池中。另外多次訪問該
servlet
可能用的不是工作執行緒池裡面的同一個執行緒,這會導致工作執行緒池裡面多個執行緒都會存在記憶體洩露。
另外,
servlet
的
doGet
方法裡面建立
new LocalVariable()
的時候使用的是
webappclassloader
。
那麼
LocalVariable
物件沒有釋放 ->
LocalVariable.class
沒有釋放 ->
webappclassloader
沒有釋放 ->
webappclassloader
載入的所有類也沒有被釋放,也造成了記憶體洩露。
除此之外,你在
eclipse
中,做一個
reload
操作,工作執行緒池裡面的執行緒還是一直存在的,並且執行緒裡面的
threadLocal
變數並沒有被清理。而
reload
的時候,又會新構建一個
webappclassloader
,重複上述步驟。多reload幾次,就記憶體溢位。
不過Tomcat7.0以後,你每做一次
reload
,會清理工作執行緒池中執行緒的
threadLocals
變數。因此,這個問題在tomcat7.0後,不會存在。
ps:
ThreadLocal
的使用在
Tomcat
的服務環境下要注意,並非每次web請求時候程式執行的
ThreadLocal
都是唯一的。
ThreadLocal
的什麼生命週期不等於一次
Request
的生命週期。
ThreadLocal
與執行緒物件緊密繫結的,由於
Tomcat
使用了執行緒池,執行緒是可能存在複用情況。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31473948/viewspace-2199439/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 淺談記憶體洩漏記憶體
- 記憶體洩露記憶體洩露
- 淺談Android開發中記憶體洩露與優化Android記憶體洩露優化
- Linux記憶體洩露案例分析和記憶體管理分享Linux記憶體洩露
- js記憶體洩露JS記憶體洩露
- JavaScript記憶體洩露JavaScript記憶體洩露
- 記憶體洩露嗎記憶體洩露
- SHBrowseForFolder 記憶體洩露記憶體洩露
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- Lowmemorykiller記憶體洩露分析記憶體洩露
- 經驗之談:記憶體洩露的原因以及分析記憶體洩露
- 使用 mtrace 分析 “記憶體洩露”記憶體洩露
- 實戰Go記憶體洩露Go記憶體洩露
- js記憶體洩露的原因JS記憶體洩露
- Java記憶體洩露的原因Java記憶體洩露
- JAVA 記憶體洩露的理解Java記憶體洩露
- IE中的記憶體洩露記憶體洩露
- 學習Java:記憶體洩露Java記憶體洩露
- 記一次Go websocket 專案記憶體洩露排查 + 使用Go pprof定位記憶體洩露GoWeb記憶體洩露
- Android 記憶體洩露詳解Android記憶體洩露
- 線上記憶體洩露定位--memleak工具記憶體洩露
- Pprof定位Go程式記憶體洩露Go記憶體洩露
- Android 檢測記憶體洩露Android記憶體洩露
- 如何處理 JavaScript 記憶體洩露JavaScript記憶體洩露
- leaks工具查詢記憶體洩露記憶體洩露
- 記憶體洩露引起的問題記憶體洩露
- MFC記憶體洩露與檢測記憶體洩露
- 如何定位和解決記憶體洩露記憶體洩露
- JavaScript中的記憶體洩露模式JavaScript記憶體洩露模式
- ThreaLocal記憶體洩露的問題記憶體洩露
- JVM與記憶體洩露問題JVM記憶體洩露
- iOS記憶體淺談iOS記憶體
- 如何避免JavaScript的記憶體洩露及記憶體管理技巧JavaScript記憶體洩露
- ArkTS 的記憶體快照與記憶體洩露除錯記憶體洩露除錯
- win10驅動記憶體洩露如何解決_win10記憶體洩露處理方法Win10記憶體洩露
- nodejs爬蟲記憶體洩露排查NodeJS爬蟲記憶體洩露
- java中如何檢視記憶體洩露Java記憶體洩露
- 解決git記憶體洩露問題Git記憶體洩露