簡單點說,當(dāng)一個對象不再被使用(即失去了利用價值),但它的引用卻依然被其他對象所持有,導(dǎo)致JVM的垃圾回收機制無法回收釋放此對象,則該無用對象繼續(xù)占用內(nèi)存空間(占著茅坑。。。),即內(nèi)存泄漏了。所有內(nèi)存都被占用且無法進(jìn)行垃圾回收時,就會發(fā)生內(nèi)存溢出。
全面點說,那要從java虛擬機運行時的數(shù)據(jù)區(qū)域說起,《深入理解java虛擬機:JVM高級特性與最佳實踐》這本書對此闡釋很明確,jvm所管理的內(nèi)存將會包括以下幾個運行時的數(shù)據(jù)區(qū)域:

這幾個數(shù)據(jù)區(qū)除程序計數(shù)器外都會發(fā)生內(nèi)存溢出問題(內(nèi)存泄露的后果是內(nèi)存溢出,但內(nèi)存溢出并不都是有內(nèi)存泄露引起的)。
程序計數(shù)器是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程執(zhí)行字節(jié)碼的行號指示器,在虛擬機的概念模型里,字節(jié)碼解釋器的工作就是通過改變程序計數(shù)器的值來選去下一條執(zhí)行的字節(jié)碼指令,分之,循環(huán),跳轉(zhuǎn),異常處理,線程恢復(fù)等基礎(chǔ)功能都需要依賴這個程序計數(shù)器來完成。每條線程都持有一個獨立的程序計數(shù)器,各個計數(shù)器之間互不影響?yīng)毩⒋鎯Γ@類內(nèi)存區(qū)域被稱為“線程私有”內(nèi)存。 如果執(zhí)行java指令,此計數(shù)器中存有當(dāng)前執(zhí)行的字節(jié)碼指令的行數(shù),若執(zhí)行native方法,則這個計數(shù)器的值為空(Undefined)。此內(nèi)存區(qū)域是唯一一個jvm規(guī)范沒有規(guī)定OutOfMemoryError的區(qū)域。
虛擬機棧也是一塊兒線程私有內(nèi)存,它描述的是java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的時候會創(chuàng)建一個幀棧,這個幀棧用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息,每一個方法調(diào)用到執(zhí)行完的過程就是幀棧從虛擬機棧入棧到出棧的過程。我們常說的“堆棧”中的棧就是指虛擬機棧,更精確的說是指虛擬機棧中存儲的局部變量表。局部變量表中存儲了編譯期可知的基本數(shù)類型,對象的引用,以及returnAddress類型——指向一條字節(jié)碼指令的地址。(long和double占兩個Slot——局部變量空間,其他類型占一個Slot) 當(dāng)進(jìn)入一個方法是,該方法對應(yīng)的局部變量空間是確定了的,并且在方法執(zhí)行期間不會改變。在jvm規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果請求的棧深度大于虛擬機所允許的最大深度,會拋出StackOverflowError;如果虛擬機可以動態(tài)擴展,并且在擴展時無法申請到足夠內(nèi)存,則或拋出OutOfMemoryError。
本地方法棧同虛擬機棧功能類似,都是為了執(zhí)行方法而服務(wù)的,只不過本地方法棧是服務(wù)于Native方法,jvm并沒有對此區(qū)域做具體的規(guī)定,同虛擬機棧一樣本地方法棧也會拋出上文兩個異常。
java堆我們很熟悉了,它是虛擬機管理的內(nèi)存區(qū)域中最大的一塊,被所有線程所共享,在虛擬機啟動時被創(chuàng)建。堆存在的唯一目的是存儲對象的實例,但隨著技術(shù)的發(fā)展,堆的作用也不是那么“絕對”了。堆也是垃圾收集器管理的主要區(qū)域,jvm規(guī)范規(guī)定堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)即可。堆的大小可以是固定的,也可以是擴展的,如果在堆中沒有完成實例分配,并且堆也無法在擴展時,將會拋出OutOfMemoryError異常。
方法區(qū)也是一塊被線程共享的區(qū)域,它用于存儲已經(jīng)被虛擬機掛在的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù),雖然Jvm規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但他卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與java堆區(qū)分開來。jvm對方法區(qū)的規(guī)范較為寬松,也可以不實現(xiàn)垃圾收集,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,會拋出OutOfMemoryError異常。
運行時常量池是方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)時存放。它同時受到方法區(qū)內(nèi)存的限制,當(dāng)其無法申請內(nèi)存時會拋出OutOfMemoryError異常。
直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,但這部分內(nèi)存也會被頻繁的使用,當(dāng)jvm動態(tài)擴展時各區(qū)域總內(nèi)存超過了物理內(nèi)存限制,也會拋出OutOfMemoryError異常。
在Android開發(fā)中我們主要關(guān)心的是java堆中的內(nèi)存泄漏問題,Android中常見的內(nèi)存泄漏有一下幾個地方: 資源使用完畢沒有關(guān)閉:數(shù)據(jù)庫cursor,流等 靜態(tài)變量、單例持有對象的引用會使該對象無法銷毀 無限循環(huán)的屬性動畫也會使activity無法銷毀 context被生命周期常于activity的對象持有導(dǎo)致activity無法銷毀 集合類持有無用對象的引用 匿名內(nèi)部類或非靜態(tài)內(nèi)部類實例會持有外部類的引用,導(dǎo)致無法回收 handler引用被MessageQueue持有直到消息被送達(dá)
| 
 
 | 
新聞熱點
疑難解答