最近在畫像項(xiàng)目里需要將某個(gè)標(biāo)簽進(jìn)行篩選谱净,但是有個(gè)操作是直接把數(shù)據(jù)從庫里面拿出千萬級(jí)別的標(biāo)簽放進(jìn)內(nèi)存中進(jìn)行遍歷操作摩疑,然后程序直接掛了匕累。于是很好奇研究了下平時(shí)代碼中會(huì)不會(huì)出現(xiàn)內(nèi)存溢出的問題渡八。
內(nèi)存泄漏與內(nèi)存溢出的概念
在long long ago,相傳編程語言存在鄙視鏈霞怀,C鄙視C++惫东,C++鄙視Java,Java鄙視c#,結(jié)果最后PHP莫名其妙成了世界上最好的語言(真香警告)毙石。在大學(xué)的時(shí)候廉沮,一開始學(xué)C++程序總是容易崩潰,因?yàn)樵贑++中徐矩,對(duì)象占用的內(nèi)存空間都必須有我們自己來顯式回收滞时,如果處理完之后忘記了回收內(nèi)存,那它們所占用的內(nèi)存空間就會(huì)產(chǎn)生內(nèi)存泄漏滤灯,很容易造成程序的崩潰坪稽。對(duì)于Java來說,JVM垃圾回收機(jī)制會(huì)自動(dòng)回收無用對(duì)象所占用的內(nèi)存空間而不需要我們手動(dòng)去釋放力喷。理論上在Java中是不存在內(nèi)存泄漏與內(nèi)存溢出的場(chǎng)景刽漂,但實(shí)際上演训,如果使用不當(dāng)弟孟,Java程序也會(huì)存在內(nèi)存泄漏的問題。
程序在運(yùn)行過程中會(huì)不斷地分配內(nèi)存样悟,那些不再使用的內(nèi)存空間應(yīng)該及時(shí)回收拂募,從而保證系統(tǒng)可以再次使用這些內(nèi)存庭猩,如果存在無用的內(nèi)存沒有被回收,那么這內(nèi)存空間就存在內(nèi)存泄漏陈症。一般來說不可達(dá)的對(duì)象都是由JVM垃圾回收機(jī)制去回收蔼水,但是有些對(duì)象處于可達(dá)狀態(tài),程序卻無法再去訪問录肯,那么這些對(duì)象所占用的內(nèi)存空間就無法進(jìn)行回收趴腋,所在空間就存在內(nèi)存泄漏。簡(jiǎn)而言之也就是被分配的對(duì)象可達(dá)卻無用论咏。
- 在垃圾回收機(jī)制中优炬,通過一系列稱為“GC Roots”的對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索厅贪,搜索所走過的路徑稱為應(yīng)用鏈蠢护,一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連,用圖論的話來說就是從GC Roots到這個(gè)對(duì)象不可達(dá)养涮,對(duì)于GC Root無法達(dá)到的對(duì)象便是垃圾對(duì)象葵硕,隨時(shí)可被GC回收。相反贯吓,可達(dá)對(duì)象便是存活對(duì)象不會(huì)被GC回收懈凹。
簡(jiǎn)單來說當(dāng)我們服務(wù)器內(nèi)存不足以給程序分配內(nèi)存空間時(shí),此時(shí)程序就會(huì)報(bào)內(nèi)存溢出錯(cuò)誤宣决。也就是內(nèi)存泄漏是誘因蘸劈,在多次積累之后會(huì)導(dǎo)致內(nèi)存溢出。
分析Java運(yùn)行時(shí)內(nèi)存情況
了解了內(nèi)存泄漏的概念之后尊沸,為了更準(zhǔn)確定位導(dǎo)致內(nèi)存泄漏的地方威沫,有必要先分析下Java在運(yùn)行時(shí)內(nèi)存的使用情況。
方法區(qū):在Java虛擬機(jī)中洼专,方法區(qū)是可供各條線程共享的運(yùn)行時(shí)的內(nèi)存區(qū)域棒掠,也就是說所有線程都可以共享。它存儲(chǔ)了每一個(gè)類的結(jié)構(gòu)信息屁商,例如烟很,運(yùn)行時(shí)常量池、字段和方法數(shù)據(jù)蜡镶、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容雾袱,還包括一些在類、實(shí)例官还、接口初始化時(shí)用到的特殊方法芹橡。方法區(qū)邏輯上屬于堆的一部分,但是為了與堆進(jìn)行區(qū)分望伦,通常又叫“非堆”林说。
在JDK1.7及以前煎殷,HotSpot里面對(duì)方法區(qū)的實(shí)現(xiàn)稱為永久代(PermGen space),為何會(huì)稱為永久代呢腿箩?主要是在之前認(rèn)為方法區(qū)存放的數(shù)據(jù)例如常量池豪直,靜態(tài)變量和 類是靜態(tài)的,幾乎不會(huì)被GC回收可以長(zhǎng)時(shí)間存放在該區(qū)域中珠移。這種情況下方法區(qū)是很容易發(fā)生內(nèi)存溢出的弓乙,例如用cblib的代態(tài)代理通過反射生成類的時(shí)候。
因此钧惧,在JDK1.8之后唆貌,HotSpot 已經(jīng)沒有 “PermGen space”這個(gè)區(qū)間了,而是用元空間(Metaspace)來代替了垢乙。本質(zhì)上永久代和元空間都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)锨咙,不過雙方有很大的區(qū)別就是方法區(qū)使用的是虛擬機(jī)的內(nèi)存,元空間使用的是本地內(nèi)存追逮。也就是你的服務(wù)器有多大內(nèi)存酪刀,元空間就可以設(shè)置多大的內(nèi)存。默認(rèn)情況下钮孵,元空間的大小僅受本地內(nèi)存限制骂倘,但可以通過以下參數(shù)來指定元空間的大小。所以使用本地內(nèi)存可以盡可能的避免內(nèi)存溢出巴席。-
堆:堆內(nèi)存也是所有線程共享的历涝,在虛擬機(jī)啟動(dòng)的時(shí)候就已經(jīng)創(chuàng)建好了。大部分對(duì)象以及數(shù)組都是在堆上分配的漾唉,為何不是所有對(duì)象都有堆分配呢荧库?在使用Java的時(shí)候,一般會(huì)認(rèn)為Java 創(chuàng)建的對(duì)象都是被分配到堆內(nèi)存上的赵刑,實(shí)際上Java中的逃逸分析可以導(dǎo)致對(duì)象不再堆中分配分衫。在《深入了解Java虛擬機(jī)》一書中有章節(jié)描述Java逃逸分析技術(shù)。這堆空間可由 GC 進(jìn)行回收般此,當(dāng)無法回收內(nèi)存空間的時(shí)候會(huì)拋出OutOfMemoryError蚪战。
虛擬機(jī)棧:每一條 Java 虛擬機(jī)線程都有自己私有的 Java 虛擬機(jī)棧,這個(gè)棧與線程同時(shí)創(chuàng)建铐懊,棧里面存著的是一種叫“棧幀”的東西邀桑。每個(gè)方法會(huì)創(chuàng)建一個(gè)棧幀,棧幀中存放了局部變量表(基本數(shù)據(jù)類型和對(duì)象引用)等信息科乎。棧幀隨著方法調(diào)用而創(chuàng)建壁畸,隨著方法結(jié)束而銷毀——無論方法是正常完成還是異常完成(拋出了在方法內(nèi)未被捕獲的異常)都算作方法結(jié)束。椣参梗可以被實(shí)現(xiàn)成固定大小瓤摧,也可以根據(jù)計(jì)算動(dòng)態(tài)來擴(kuò)展和收縮。如果線程請(qǐng)求分配的棧容量超過 Java 虛擬機(jī)棧允許的最大容量玉吁,Java 虛擬機(jī)將會(huì)拋出一個(gè)StackOverflowError 異常照弥。
內(nèi)存泄漏與內(nèi)存溢出場(chǎng)景
- 內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù)进副,這就會(huì)造成內(nèi)存溢出問題这揣。
-
長(zhǎng)生命周期對(duì)象持有短生命周期對(duì)象的引用。造成內(nèi)存泄漏
上述demo中影斑,由于obj對(duì)象一直被靜態(tài)map引用给赞,對(duì)于GC來說,該對(duì)象可達(dá)但無用矫户,這就存在內(nèi)存泄漏片迅。
-
代碼中存在循環(huán)產(chǎn)生過多重復(fù)的對(duì)象實(shí)體。造成堆內(nèi)存溢出(OutOfMemoryError: Java heap space)
上述demo中皆辽,造成了堆內(nèi)存溢出柑蛇。在開始創(chuàng)建對(duì)象時(shí)候,對(duì)象只會(huì)存在于Eden區(qū)和Survivor區(qū)驱闷,當(dāng)Eden區(qū)不夠空間的時(shí)候就會(huì)觸發(fā)Minor GC將對(duì)象復(fù)制到Survior區(qū)耻台,當(dāng)Survior區(qū)不夠的時(shí)候就會(huì)復(fù)制到老年代。當(dāng)老年代空間不夠就會(huì)觸發(fā)Full GC對(duì)整個(gè)堆進(jìn)行Full GC空另,但是這次Full GC并不會(huì)回收對(duì)象的軟連接盆耽。如果還不夠空間就會(huì)再次進(jìn)行Full GC,這時(shí)會(huì)對(duì)軟連接進(jìn)行回收扼菠。所以當(dāng)你的項(xiàng)目頻繁進(jìn)行Full GC的時(shí)候摄杂,很有可能存在內(nèi)存泄漏。
- 當(dāng)加載到方法區(qū)的class太多的時(shí)候就可能會(huì)報(bào)出permgen溢出的錯(cuò)誤循榆。(OutOfMemoryError: PermGen space)特別是用cglib動(dòng)態(tài)生成類 的時(shí)候匙姜。
-
代碼中存在死循環(huán)或遞歸調(diào)用,會(huì)產(chǎn)生棧溢出冯痢。(StackOverflowError)
- 對(duì)文件流氮昧,數(shù)據(jù)庫連接流等操作沒有關(guān)閉流(emmmmm 靜態(tài)工具檢查最多這個(gè)問題。浦楣。)
排查方式
- 生成dump文件袖肥,借助工具例如MAT進(jìn)行分析
- 利用jps命令查找Java進(jìn)程pid
- 使用命令top -p <pid> ,顯示Java進(jìn)程的內(nèi)存情況
- jstat -gccause pid 10000 每各10秒輸出結(jié)果