這篇文章主要介紹“ThreadLocal在Tomcat中引起內(nèi)存泄露怎么解決”,在日常操作中,相信很多人在ThreadLocal在Tomcat中引起內(nèi)存泄露怎么解決問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”ThreadLocal在Tomcat中引起內(nèi)存泄露怎么解決”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)公司主要從事網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)高碑店,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792
首先,我們要先談一下定義,因?yàn)橐欢讶烁悴欢畠?nèi)存溢出和內(nèi)存泄露的區(qū)別。
內(nèi)存溢出(OutOfMemory):你只有十塊錢,我卻找你要了一百塊。對不起啊,我沒有這么多錢。(給不起)
內(nèi)存泄露(MemoryLeak):你有十塊錢,我找你要一塊。但是無恥的博主,不把錢還你了。(沒退還)
關(guān)系:多次的內(nèi)存泄露,會導(dǎo)致內(nèi)存溢出。(博主不要臉的找你多要幾次錢,你就沒錢了,就是這個(gè)道理。)
ok,大家在項(xiàng)目中有沒遇到過java程序越來越卡的情況。
因?yàn)閮?nèi)存泄露,會導(dǎo)致頻繁的Full GC
,而Full GC
又會造成程序停頓,最后Crash了。因此,你會感覺到你的程序越來越卡,越來越卡,然后你就被產(chǎn)品經(jīng)理鄙視了。順便提一下,我們之所以JVM調(diào)優(yōu),就是為了減少Full GC
的出現(xiàn)。
我記得,我曾經(jīng)有一次,就遇到項(xiàng)目剛上線的時(shí)候好好的。結(jié)果隨著時(shí)間的堆積,報(bào)了OutOfMemoryError: PermGen space
。
說到這個(gè)PermGen space
,突然間,一陣洪荒之力,從博主體內(nèi)噴涌而出,一定要介紹一下這個(gè)方法區(qū),不過點(diǎn)到為止,畢竟這不是在講《jvm從入門到放棄》。
方法區(qū):出自java虛擬機(jī)規(guī)范, 可供各條線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域。它存儲了每一個(gè)類的結(jié)構(gòu)信息,例如運(yùn)行時(shí)常量池(Runtime Constant Pool
)、字段和方法數(shù)據(jù)、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容。
上面講的是規(guī)范,在不同虛擬機(jī)里頭實(shí)現(xiàn)是不一樣的,最典型的就是永久代(PermGen space)和元空間(Metaspace)。
jdk1.8以前:實(shí)現(xiàn)方法區(qū)的叫永久代。因?yàn)樵诤芫眠h(yuǎn)以前,java覺得類幾乎是靜態(tài)的,并且很少被卸載和回收,所以給了一個(gè)永久代的雅稱。因此,如果你在項(xiàng)目中,發(fā)現(xiàn)堆和永久代一直在不斷增長,沒有下降趨勢,回收的速度根本趕不上增長的速度,不用說了,這種情況基本可以確定是內(nèi)存泄露。
jdk1.8以后:實(shí)現(xiàn)方法區(qū)的叫元空間。Java覺得對永久代進(jìn)行調(diào)優(yōu)是很困難的。永久代中的元數(shù)據(jù)可能會隨著每一次Full GC
發(fā)生而進(jìn)行移動。并且為永久代設(shè)置空間大小也是很難確定的。因此,java決定將類的元數(shù)據(jù)分配在本地內(nèi)存中,元空間的最大可分配空間就是系統(tǒng)可用內(nèi)存空間。這樣,我們就避開了設(shè)置永久代大小的問題。但是,這種情況下,一旦發(fā)生內(nèi)存泄露,會占用你的大量本地內(nèi)存。如果你發(fā)現(xiàn),你的項(xiàng)目中本地內(nèi)存占用率異常高。嗯,這就是內(nèi)存泄露了。
(1)通過jps
查找java進(jìn)程id。
(2)通過top -p [pid]
發(fā)現(xiàn)內(nèi)存占用達(dá)到了最大值
(3)jstat -gccause pid 20000
每隔20秒輸出Full GC
結(jié)果
(4)發(fā)現(xiàn)Full GC
次數(shù)太多,基本就是內(nèi)存泄露了。生成dump
文件,借助工具分析是哪個(gè)對象太多了?;灸芏ㄎ坏絾栴}在哪。
在stackoverflow上,有一個(gè)問題,如下所示
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.
大致就是,因?yàn)槊嬖囆枰謱懸欢蝺?nèi)存泄露的程序,然后提問的人突然懵逼了,于是很多大佬紛紛給出回答。
案例一
此例子出自《算法》(第四版)一書,我簡化了一下
class stack{ Object data[1000]; int top = 0; public void push(Object o){ data[top++] = o; } public Object pop(Object o){ return data[--top]; } }
當(dāng)數(shù)據(jù)從棧里面彈出來之后,data數(shù)組還一直保留著指向元素的指針。那么就算你把棧pop空了,這些元素占的內(nèi)存也不會被回收的。
解決方案就是
public Object pop(Object o){ Object result = data[--top]; data[top] = null; return result; }
案例二
這個(gè)其實(shí)是一堆例子,這些例子造成內(nèi)存泄露的原因都是類似的,就是不關(guān)閉流,具體的,可以是文件流,socket流,數(shù)據(jù)庫連接流,等等
具體如下,沒關(guān)文件流
try { BufferedReader br = new BufferedReader(new FileReader(inputFile)); ... ... } catch (Exception e) { e.printStacktrace(); }
再比如,沒關(guān)閉連接
try { Connection conn = ConnectionFactory.getConnection(); ... ... } catch (Exception e) { e.printStacktrace(); }
解決方案就是。。。嗯,大家應(yīng)該都會。。你敢說你不會調(diào)close()
方法。
案例三
講這個(gè)例子前,大家對ThreadLocal
在Tomcat
中引起內(nèi)存泄露有了解么。不過,我要說一下,這個(gè)泄露問題,和ThreadLocal本身關(guān)系不大,我看了一下官網(wǎng)給的例子,基本都是屬于使用不當(dāng)引起的。
在Tomcat的官網(wǎng)上,記錄了這個(gè)問題。地址是:https://wiki.apache.org/tomcat/MemoryLeakProtection
不過,官網(wǎng)的這個(gè)例子,可能不好理解,我們略作改動。
public class HelloServlet extends HttpServlet{ private static final long serialVersionUID = 1L; static class LocalVariable { private Long[] a = new Long[1024 * 1024 * 100]; } final static ThreadLocallocalVariable = new ThreadLocal (); @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { localVariable.set(new LocalVariable()); } }
再來看下conf下sever.xml配置
線程池最大線程為150個(gè),最小線程為4個(gè)
Tomcat中Connector組件負(fù)責(zé)接受并處理請求,每來一個(gè)請求,就會去線程池中取一個(gè)線程。
在訪問該servlet時(shí),ThreadLocal
變量里面被添加了new LocalVariable()
實(shí)例,但是沒有被remove
,這樣該變量就隨著線程回到了線程池中。另外多次訪問該servlet
可能用的不是工作線程池里面的同一個(gè)線程,這會導(dǎo)致工作線程池里面多個(gè)線程都會存在內(nèi)存泄露。
另外,servlet
的doGet
方法里面創(chuàng)建new LocalVariable()
的時(shí)候使用的是webappclassloader
。
那么LocalVariable
對象沒有釋放 -> LocalVariable.class
沒有釋放 ->webappclassloader
沒有釋放 -> webappclassloader
加載的所有類也沒有被釋放,也造成了內(nèi)存泄露。
除此之外,你在eclipse
中,做一個(gè)reload
操作,工作線程池里面的線程還是一直存在的,并且線程里面的threadLocal
變量并沒有被清理。而reload
的時(shí)候,又會新構(gòu)建一個(gè)webappclassloader
,重復(fù)上述步驟。多reload幾次,就內(nèi)存溢出。
不過Tomcat7.0以后,你每做一次reload
,會清理工作線程池中線程的threadLocals
變量。因此,這個(gè)問題在tomcat7.0后,不會存在。
ps:ThreadLocal
的使用在Tomcat
的服務(wù)環(huán)境下要注意,并非每次web請求時(shí)候程序運(yùn)行的ThreadLocal
都是唯一的。ThreadLocal
的什么生命周期不等于一次Request
的生命周期。ThreadLocal
與線程對象緊密綁定的,由于Tomcat
使用了線程池,線程是可能存在復(fù)用情況。
到此,關(guān)于“ThreadLocal在Tomcat中引起內(nèi)存泄露怎么解決”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!