JVM内存管理概述与android内存泄露分析
?一.內(nèi)存劃分
 將內(nèi)存劃分為六大部分,分別是PC寄存器、JAVA虛擬機(jī)棧、JAVA堆、方法區(qū)、運(yùn)行時(shí)常量池以及本地方法棧.
 1、PC寄存器(線程獨(dú)有):全稱是程序計(jì)數(shù)寄存器,它記載著每一個(gè)線程當(dāng)前運(yùn)行的JAVA方法的地址,
 如果是當(dāng)前執(zhí)行的是本地方法,則程序計(jì)數(shù)器會(huì)是一個(gè)空地址。它的作用就是用來支持多線程,線程的阻塞、恢復(fù)、
 掛起等一系列操作,直觀的想象一下,要是沒有記住每個(gè)線程當(dāng)前運(yùn)行的位置,又如何恢復(fù)呢。依據(jù)這一點(diǎn),
 每一個(gè)線程都有一個(gè)PC寄存器,也就是說PC寄存器是線程獨(dú)有的。
 2、JAVA虛擬機(jī)棧(線程獨(dú)有):JAVA虛擬機(jī)棧是在創(chuàng)建線程的同時(shí)創(chuàng)建的,用于存儲(chǔ)棧幀,
 JAVA虛擬機(jī)棧也是線程獨(dú)有的。
 3、JAVA堆(全局共享):這一部分是JAVA內(nèi)存中最重要的一部分,之所以說是最重要的一部分,
 并不是因?yàn)樗闹匾?#xff0c;而是指作為開發(fā)人員最應(yīng)該關(guān)注的一部分。它隨著JAVA虛擬機(jī)的啟動(dòng)創(chuàng)建,
 儲(chǔ)存著所有對(duì)象實(shí)例以及數(shù)組對(duì)象,而且內(nèi)置了“自動(dòng)內(nèi)存管理系統(tǒng)”,也就是我們常說的垃圾搜集器(GC)。
 JAVA堆中的內(nèi)存釋放是不受開發(fā)人員控制的,完全由JAVA虛擬機(jī)一手操辦。對(duì)于JAVA虛擬機(jī)如何實(shí)現(xiàn)垃圾搜集器,
 JAVA虛擬機(jī)規(guī)范沒有明確的規(guī)定,也正因如此,我們平時(shí)使用的JAVA虛擬機(jī)中提供了許多種垃圾搜集器,
 它們采用不同的算法以及實(shí)現(xiàn)方式,已滿足多方面的性能需求。
 4、方法區(qū)(全局共享):方法區(qū)也是堆的一個(gè)組成部分,它主要存儲(chǔ)的是運(yùn)行時(shí)常量池、字段信息、方法信息、
 構(gòu)造方法與普通函數(shù)的字節(jié)碼內(nèi)容以及一些特殊方法。它與JAVA堆的區(qū)別除了存儲(chǔ)的信息與JAVA堆不一樣之外,
 最大的區(qū)別就是這一部分JAVA虛擬機(jī)規(guī)范不強(qiáng)制要求實(shí)現(xiàn)自動(dòng)內(nèi)存管理系統(tǒng)(GC)。
 5、本地方法棧(線程獨(dú)有):本地方法棧是一個(gè)傳統(tǒng)的棧,它用來支持native方法的執(zhí)行。
 如果JAVA虛擬機(jī)是使用的其它語言實(shí)現(xiàn)指令集解釋器的時(shí)候,也會(huì)用到本地方法棧。如果前面這兩種都未發(fā)生,
 也就是說如果JAVA虛擬機(jī)不依賴于本地方法棧,而且JAVA虛擬機(jī)也不支持native方法,則不需要本地方法棧。
 而如果需要的話,則本地方法棧也是隨每一個(gè)線程的啟動(dòng)而創(chuàng)建的。
上面五個(gè)內(nèi)存區(qū)域,除了PC寄存器之外,其余四個(gè)一般情況下,都要求JAVA虛擬機(jī)實(shí)現(xiàn)提供給客戶調(diào)節(jié)大小的參數(shù),
也就是我們常用的Xms、Xmx等等。
Java 內(nèi)存分配策略
Java 程序運(yùn)行時(shí)的內(nèi)存分配策略有三種,分別是靜態(tài)分配,棧式分配,和堆式分配,對(duì)應(yīng)的,三種存儲(chǔ)策略使用的內(nèi)存空間主要分別是靜態(tài)存儲(chǔ)區(qū)(也稱方法區(qū))、棧區(qū)和堆區(qū)。
靜態(tài)存儲(chǔ)區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)、全局 static 數(shù)據(jù)和常量。這塊內(nèi)存在程序編譯時(shí)就已經(jīng)分配好,并且在程序整個(gè)運(yùn)行期間都存在。?
棧區(qū) :當(dāng)方法被執(zhí)行時(shí),方法體內(nèi)的局部變量都在棧上創(chuàng)建,并在方法執(zhí)行結(jié)束時(shí)這些局部變量所持有的內(nèi)存將會(huì)自動(dòng)被釋放。因?yàn)闂?nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。?
堆區(qū) : 又稱動(dòng)態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時(shí)直接 new 出來的內(nèi)存。這部分內(nèi)存在不使用時(shí)將會(huì)由 Java 垃圾回收器來負(fù)責(zé)回收。?
棧與堆的區(qū)別:
在方法體內(nèi)定義的(局部變量)一些基本類型的變量和對(duì)象的引用變量都是在方法的棧內(nèi)存中分配的。當(dāng)在一段方法塊中定義一個(gè)變量時(shí),Java 就會(huì)在棧中為該變量分配內(nèi)存空間,當(dāng)超過該變量的作用域后,該變量也就無效了,分配給它的內(nèi)存空間也將被釋放掉,該內(nèi)存空間可以被重新使用。
堆內(nèi)存用來存放所有由 new 創(chuàng)建的對(duì)象(包括該對(duì)象其中的所有成員變量)和數(shù)組。在堆中分配的內(nèi)存,將由 Java 垃圾回收器來自動(dòng)管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,這個(gè)特殊的變量就是我們上面說的引用變量。我們可以通過這個(gè)引用變量來訪問堆中的對(duì)象或者數(shù)組。
?
四種引用類型的介紹
強(qiáng)引用(StrongReference):JVM 寧可拋出 OOM ,也不會(huì)讓 GC 回收具有強(qiáng)引用的對(duì)象;
軟引用(SoftReference):只有在內(nèi)存空間不足時(shí),才會(huì)被回的對(duì)象;
弱引用(WeakReference):在 GC 時(shí),一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存;
虛引用(PhantomReference):任何時(shí)候都可以被GC回收,當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。程序可以通過判斷引用隊(duì)列中是否存在該對(duì)象的虛引用,來了解這個(gè)對(duì)象是否將要被回收??梢杂脕碜鳛镚C回收Object的標(biāo)志。
我們常說的內(nèi)存泄漏是指new出來的Object無法被GC回收,即為強(qiáng)引用:
什么是Java中的內(nèi)存泄露
對(duì)于C++來說,內(nèi)存泄漏就是new出來的對(duì)象沒有delete,俗稱野指針;對(duì)于Java來說,就是new出來的Object 放在Heap上無法被GC回收;
在Java中,內(nèi)存泄漏就是存在一些被分配的對(duì)象,這些對(duì)象有下面兩個(gè)特點(diǎn),首先,這些對(duì)象是可達(dá)的,即在有向圖中,存在通路可以與其相連;其次,這些對(duì)象是無用的,即程序以后不會(huì)再使用這些對(duì)象。如果對(duì)象滿足這兩個(gè)條件,這些對(duì)象就可以判定為Java中的內(nèi)存泄漏,這些對(duì)象不會(huì)被GC所回收,然而它卻占用內(nèi)存。
在C++中,內(nèi)存泄漏的范圍更大一些。有些對(duì)象被分配了內(nèi)存空間,然后卻不可達(dá),由于C++中沒有GC,這些內(nèi)存將永遠(yuǎn)收不回來。在Java中,這些不可達(dá)的對(duì)象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄露。
通過分析,我們得知,對(duì)于C++,程序員需要自己管理邊和頂點(diǎn),而對(duì)于Java程序員只需要管理邊就可以了(不需要管理頂點(diǎn)的釋放)。通過這種方式,Java提高了編程的效率。
因此,通過以上分析,我們知道在Java中也有內(nèi)存泄漏,但范圍比C++要小一些。因?yàn)镴ava從語言上保證,任何對(duì)象都是可達(dá)的,所有的不可達(dá)對(duì)象都由GC管理。
對(duì)于程序員來說,GC基本是透明的,不可見的。雖然,我們只有幾個(gè)函數(shù)可以訪問GC,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語言規(guī)范定義, 該函數(shù)不保證JVM的垃圾收集器一定會(huì)執(zhí)行。因?yàn)?#xff0c;不同的JVM實(shí)現(xiàn)者可能使用不同的算法管理GC。通常,GC的線程的優(yōu)先級(jí)別較低。JVM調(diào)用GC的策略也有很多種,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開始工作,也有定時(shí)執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來說,我們不需要關(guān)心這些。除非在一些特定的場(chǎng)合,GC的執(zhí)行影響應(yīng)用程序的性能,例如對(duì)于基于Web的實(shí)時(shí)系統(tǒng),如網(wǎng)絡(luò)游戲等,用戶不希望GC突然中斷應(yīng)用程序執(zhí)行而進(jìn)行垃圾回收,那么我們需要調(diào)整GC的參數(shù),讓GC能夠通過平緩的方式釋放內(nèi)存,例如將垃圾回收分解為一系列的小步驟執(zhí)行,Sun提供的HotSpot JVM就支持這一特性。
同樣給出一個(gè) Java 內(nèi)存泄漏的典型例子,
Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }在這個(gè)例子中,我們循環(huán)申請(qǐng)Object對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè) Vector 中,如果我們僅僅釋放引用本身,那么 Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì) GC 來說是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從 Vector 中刪除,最簡(jiǎn)單的方法就是將 Vector 對(duì)象設(shè)置為 null。
?
Android中常見的內(nèi)存泄漏匯總?
什么是內(nèi)存溢出:(Out Of Memory,簡(jiǎn)稱 OOM),通俗理解就是內(nèi)存不夠,即內(nèi)存占用超出內(nèi)存的空間大小;基本上會(huì)造成程序奔潰。解決:找到報(bào)錯(cuò)的代碼并解決。
什么是內(nèi)存泄露:內(nèi)存泄漏(Memory Leak),簡(jiǎn)單理解就是內(nèi)存使用完畢之后本該垃圾回收卻未被回收。(占著茅坑不拉屎)--內(nèi)存無法回收又沒起作用。
? ??
推理!!!
1.看對(duì)象的大小;
2.深入使用MAT進(jìn)行對(duì)象細(xì)節(jié)的分析
3.前后對(duì)比。
懷疑某一段代碼會(huì)造成內(nèi)存溢出??梢韵茸?cè)掉該段代碼生成一個(gè)xxx.hroph文件,然后在解開這段代碼再生成一個(gè)yyy.hroph文件
通過MAT工具對(duì)比他們的內(nèi)存消耗情況---甚至可以精確到每一個(gè)對(duì)象消耗內(nèi)存的情況。
?
?在 Android 中,泄露 Context 對(duì)象的問題尤其嚴(yán)重,特別像 Activity 這樣的 Context 對(duì)象會(huì)引用大量很占用內(nèi)存的對(duì)象,如果 Context 對(duì)象發(fā)生了內(nèi)存泄漏,那它所引用的所有對(duì)象都被泄漏了。Activity 是非常重量級(jí)的對(duì)象,所以我們應(yīng)該極力避免妨礙系統(tǒng)對(duì)其進(jìn)行回收,然而實(shí)際情況是有多種方式會(huì)無意間就泄露了Activity 對(duì)象。
case 0.靜態(tài)變量造成的內(nèi)存泄漏
?最簡(jiǎn)單的泄漏 Activity 就是在 Activity 類中定義一個(gè) static 變量,并將其指向一個(gè)運(yùn)行中的 Activity 實(shí)例。如果在 Activity 的生命周期結(jié)束之前,沒有清除這個(gè)引用,那它就會(huì)泄漏。由于 Activity 的類對(duì)象是靜態(tài)的,一旦加載,就會(huì)在 APP 運(yùn)行時(shí)一直常駐內(nèi)存,如果類對(duì)象不卸載,其靜態(tài)成員就不會(huì)被垃圾回收。
盡量避免使用 static 成員變量:
這里修復(fù)的方法是: 
不要在類初始時(shí)初始化靜態(tài)成員??梢钥紤]lazy初始化。
case 1. 單例造成的內(nèi)存泄露
單例的靜態(tài)特性導(dǎo)致其生命周期同應(yīng)用一樣長(zhǎng)
另一種類似的情況是對(duì)經(jīng)常啟動(dòng)的 Activity 實(shí)現(xiàn)一個(gè)單例模式,讓其常駐內(nèi)存可以使它能夠快速恢復(fù)狀態(tài)。
????如我們有一個(gè)創(chuàng)建起來非常耗時(shí)的 View,在同一個(gè) Activity 不同的生命周期中都保持不變呢,就為它實(shí)現(xiàn)一個(gè)單例模式。一旦 View 被加載到界面中,它就會(huì)持有 Context 的強(qiáng)引用,也就是我們的 Activity 對(duì)象。
????由于我們是通過一個(gè)靜態(tài)成員引用了這個(gè) View,所以我們也就引用了 Activity,因此 Activity 就發(fā)生了泄漏。所以一定不要把加載的 View 賦值給靜態(tài)變量,如果你真的需要,那一定要確保在 Activity 銷毀之前將其從 View 層級(jí)中移除。
解決方案:
將該屬性的引用方式改為弱引用;
如果傳入Context,使用ApplicationContext;
case 2. InnerClass匿名內(nèi)部類
我們經(jīng)常在 Activity 內(nèi)部定義一個(gè)內(nèi)部類,這樣做可以增加封裝性和可讀性。但是如果當(dāng)我們創(chuàng)建了一個(gè)內(nèi)部類的對(duì)象,并通過靜態(tài)變量持有了 Activity 的引用,那也會(huì)可能發(fā)生 Activity 泄漏。
在Java中,非靜態(tài)內(nèi)部類 和 匿名類 都會(huì)潛在的引用它們所屬的外部類,但是,靜態(tài)內(nèi)部類卻不會(huì)。如果這個(gè)非靜態(tài)內(nèi)部類實(shí)例做了一些耗時(shí)的操作,就會(huì)造成外圍對(duì)象不會(huì)被回收,從而導(dǎo)致內(nèi)存泄漏。
解決方案:
將內(nèi)部類變成靜態(tài)內(nèi)部類;
靜態(tài)內(nèi)部類中使用弱引用來引用外部類的成員變量;?
如果有強(qiáng)引用Activity中的屬性,則將該屬性的引用方式改為弱引用;
在業(yè)務(wù)允許的情況下,當(dāng)Activity執(zhí)行onDestory時(shí),結(jié)束這些耗時(shí)任務(wù);
case 3. 線程造成的內(nèi)存泄漏
?在 Activity 內(nèi)定義了一個(gè)匿名的 AsyncTask 對(duì)象,就有可能發(fā)生內(nèi)存泄漏。如果 Activity 被銷毀之后 AsyncTask 仍然在執(zhí)行,那就會(huì)阻止垃圾回收器回收Activity 對(duì)象,進(jìn)而導(dǎo)致內(nèi)存泄漏,直到執(zhí)行結(jié)束才能回收 Activity。
? ? 同樣的,使用 Thread 和 TimerTask 也可能導(dǎo)致 Activity 泄漏。只要它們是通過匿名類創(chuàng)建的,盡管它們?cè)趩为?dú)的線程被執(zhí)行,它們也會(huì)持有對(duì) Activity 的強(qiáng)引用,進(jìn)而導(dǎo)致內(nèi)存泄漏。
在Android里面線程最容易造成內(nèi)存泄露。線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控
2.線程問題的改進(jìn)方式主要有:
? ?1)將線程的內(nèi)部類,改為靜態(tài)內(nèi)部類。
? ?2)在程序中盡量采用弱引用保存Context。
case 4. Activity Context 的不正確使用
在Android應(yīng)用程序中通??梢允褂脙煞NContext對(duì)象:Activity和Application。當(dāng)類或方法需要Context對(duì)象的時(shí)候常見的做法是使用第一個(gè)作為Context參數(shù)。這樣就意味著View對(duì)象對(duì)整個(gè)Activity保持引用,因此也就保持對(duì)Activty的所有的引用。
假設(shè)一個(gè)場(chǎng)景,當(dāng)應(yīng)用程序有個(gè)比較大的Bitmap類型的圖片,每次旋轉(zhuǎn)是都重新加載圖片所用的時(shí)間較多。為了提高屏幕旋轉(zhuǎn)是Activity的創(chuàng)建速度,最簡(jiǎn)單的方法時(shí)將這個(gè)Bitmap對(duì)象使用Static修飾。 當(dāng)一個(gè)Drawable綁定在View上,實(shí)際上這個(gè)View對(duì)象就會(huì)成為這份Drawable的一個(gè)Callback成員變量。而靜態(tài)變量的生命周期要長(zhǎng)于Activity。導(dǎo)致了當(dāng)旋轉(zhuǎn)屏幕時(shí),Activity無法被回收,而造成內(nèi)存泄露。
解決方案:
使用ApplicationContext代替ActivityContext,因?yàn)锳pplicationContext會(huì)隨著應(yīng)用程序的存在而存在,而不依賴于activity的生命周期;
對(duì)Context的引用不要超過它本身的生命周期,慎重的對(duì)Context使用“static”關(guān)鍵字。Context里如果有線程,一定要在onDestroy()里及時(shí)停掉。
case 5. Handler引起的內(nèi)存泄漏
定義一個(gè)匿名的 Runnable 對(duì)象并將其提交到 Handler 上也可能導(dǎo)致 Activity 泄漏。Runnable 對(duì)象間接地引用了定義它的 Activity 對(duì)象,而它會(huì)被提交到Handler 的 MessageQueue 中,如果它在 Activity 銷毀時(shí)還沒有被處理,就會(huì)導(dǎo)致 Activity 泄漏。
當(dāng)Handler中有延遲的的任務(wù)或是等待執(zhí)行的任務(wù)隊(duì)列過長(zhǎng),由于消息持有對(duì)Handler的引用,而Handler又持有對(duì)其外部類的潛在引用,這條引用關(guān)系會(huì)一直保持到消息得到處理,而導(dǎo)致了Activity無法被垃圾回收器回收,而導(dǎo)致了內(nèi)存泄露。
修復(fù)方法:在 Activity 中避免使用非靜態(tài)內(nèi)部類,比如上面我們將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無關(guān)了。同時(shí)通過弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進(jìn)去,見下面代碼:
Handler 的持有的引用對(duì)象最好使用弱引用,資源釋放時(shí)也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的時(shí)候,取消掉該 Handler 對(duì)象的 Message和 Runnable.
綜述,即推薦使用靜態(tài)內(nèi)部類 + WeakReference 這種方式。每次使用前注意判空。
解決方案:
可以把Handler類放在單獨(dú)的類文件中,或者使用靜態(tài)內(nèi)部類便可以避免泄露;
如果想在Handler內(nèi)部去調(diào)用所在的Activity,那么可以在handler內(nèi)部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式來達(dá)到斷開Handler與Activity之間存在引用關(guān)系的目的。
case 6. 注冊(cè)監(jiān)聽器的泄漏(資源未關(guān)閉造成的內(nèi)存泄漏)
如系統(tǒng)服務(wù)可以通過 context.getSystemService 獲取,它們負(fù)責(zé)執(zhí)行某些后臺(tái)任務(wù),或者為硬件訪問提供接口。如果 Context 對(duì)象想要在服務(wù)內(nèi)部的事件發(fā)生時(shí)被通知,那就需要把自己注冊(cè)到服務(wù)的監(jiān)聽器中。然而,這會(huì)讓服務(wù)持有 Activity 的引用,如果開發(fā)者忘記在 Activity 銷毀時(shí)取消注冊(cè),也會(huì)導(dǎo)致 Activity泄漏
系統(tǒng)服務(wù)可以通過Context.getSystemService 獲取,它們負(fù)責(zé)執(zhí)行某些后臺(tái)任務(wù),或者為硬件訪問提供接口。如果Context 對(duì)象想要在服務(wù)內(nèi)部的事件發(fā)生時(shí)被通知,那就需要把自己注冊(cè)到服務(wù)的監(jiān)聽器中。然而,這會(huì)讓服務(wù)持有Activity 的引用,如果在Activity onDestory時(shí)沒有釋放掉引用就會(huì)內(nèi)存泄漏。
解決方案:
使用ApplicationContext代替ActivityContext;
在Activity執(zhí)行onDestory時(shí),調(diào)用反注冊(cè);
case 7. Cursor,Stream沒有close,View沒有recyle
資源性對(duì)象比如(Cursor,File文件等)往往都用了一些緩沖,我們?cè)诓皇褂玫臅r(shí)候,應(yīng)該及時(shí)關(guān)閉它們,以便它們的緩沖及時(shí)回收內(nèi)存。它們的緩沖不僅存在于 java虛擬機(jī)內(nèi),還存在于java虛擬機(jī)外。如果我們僅僅是把它的引用設(shè)置為null,而不關(guān)閉它們,往往會(huì)造成內(nèi)存泄漏。因?yàn)橛行┵Y源性對(duì)象,比如SQLiteCursor(在析構(gòu)函數(shù)finalize(),如果我們沒有關(guān)閉它,它自己會(huì)調(diào)close()關(guān)閉),如果我們沒有關(guān)閉它,系統(tǒng)在回收它時(shí)也會(huì)關(guān)閉它,但是這樣的效率太低了。因此對(duì)于資源性對(duì)象在不使用的時(shí)候,應(yīng)該調(diào)用它的close()函數(shù),將其關(guān)閉掉,然后才置為null. 在我們的程序退出時(shí)一定要確保我們的資源性對(duì)象已經(jīng)關(guān)閉。
對(duì)于使用了BraodcastReceiver,ContentObserver,File,游標(biāo) Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷,否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏。
Solution:
調(diào)用onRecycled()
case 8. 集合中對(duì)象沒清理造成的內(nèi)存泄漏
我們通常把一些對(duì)象的引用加入到了集合容器(比如ArrayList)中,當(dāng)我們不需要該對(duì)象時(shí),并沒有把它的引用從集合中清理掉,這樣這個(gè)集合就會(huì)越來越大。如果這個(gè)集合是static的話,那情況就更嚴(yán)重了。
所以要在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。
解決方案:
在Activity退出之前,將集合里的東西clear,然后置為null,再退出程序。
Solution
private List<EmotionPanelInfo> data; ? ?public void onDestory() { ? ? ? ?
? ?if (data != null) { ? ? ? ?data.clear(); ? ? ? ?data = null; ? ?} }
case 9. WebView造成的泄露
當(dāng)我們不要使用WebView對(duì)象時(shí),應(yīng)該調(diào)用它的destory()函數(shù)來銷毀它,并釋放其占用的內(nèi)存,否則其占用的內(nèi)存長(zhǎng)期也不能被回收,從而造成內(nèi)存泄露。
解決方案:
為webView開啟另外一個(gè)進(jìn)程,通過AIDL與主線程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷毀,從而達(dá)到內(nèi)存的完整釋放。
case 10. 構(gòu)造Adapter時(shí),沒有使用緩存的ConvertView
初始時(shí)ListView會(huì)從Adapter中根據(jù)當(dāng)前的屏幕布局實(shí)例化一定數(shù)量的View對(duì)象,同時(shí)ListView會(huì)將這些View對(duì)象 緩存起來。
當(dāng)向上滾動(dòng)ListView時(shí),原先位于最上面的List Item的View對(duì)象會(huì)被回收,然后被用來構(gòu)造新出現(xiàn)的最下面的List Item。
這個(gè)構(gòu)造過程就是由getView()方法完成的,getView()的第二個(gè)形參View ConvertView就是被緩存起來的List Item的View對(duì)象(初始化時(shí)緩存中沒有View對(duì)象則ConvertView是null)。
?
case 11.動(dòng)畫
在屬性動(dòng)畫中有一類無限循環(huán)動(dòng)畫,如果在Activity中播放這類動(dòng)畫并且在onDestroy中去停止動(dòng)畫,那么這個(gè)動(dòng)畫將會(huì)一直播放下去,這時(shí)候Activity會(huì)被View所持有,從而導(dǎo)致Activity無法被釋放。解決此類問題則是需要早Activity中onDestroy去去調(diào)用objectAnimator.cancel()來停止動(dòng)畫
case 12.第三方庫使用不當(dāng)
對(duì)于EventBus,RxJava等一些第三開源框架的使用,若是在Activity銷毀之前沒有進(jìn)行解除訂閱將會(huì)導(dǎo)致內(nèi)存泄漏。
?
綜上所述,要避免內(nèi)存泄露或者內(nèi)存溢出,主要要遵循以下幾點(diǎn):
第一:不要為Context長(zhǎng)期保存引用(要引用Context就要使得引用對(duì)象和它本身的生命周期保持一致,即對(duì)activity的引用應(yīng)該控制在activity的生命周期之內(nèi))。
第二:如果要使用到Context,盡量使用ApplicationContext去代替Context,因?yàn)锳pplicationContext的生命周期較長(zhǎng),引用情況下不會(huì)造成內(nèi)存泄露問題
第三:在你不控制對(duì)象的生命周期的情況下避免在你的Activity中使用static變量。盡量使用WeakReference去代替一個(gè)static。
第四:垃圾回收器并不保證能準(zhǔn)確回收內(nèi)存,這樣在使用自己需要的內(nèi)容時(shí),主要生命周期和及時(shí)釋放掉不需要的對(duì)象。盡量在Activity的生命周期結(jié)束時(shí),在onDestroy中把我們做引用的其他對(duì)象做資源釋放,比如:cursor.close()。如清空對(duì)圖片等資源有直接引用或者間接引用的數(shù)組(使用array.clear();array = null);
第五:盡量不要在Activity中使用非靜態(tài)內(nèi)部類,因?yàn)榉庆o態(tài)內(nèi)部類會(huì)隱式持有外部類實(shí)例的引用。如果使用靜態(tài)內(nèi)部類,將外部實(shí)例引用作為弱引用持有。
(靜態(tài)的內(nèi)部類不會(huì)持有外部類的一個(gè)隱式引用)
在 Java 的實(shí)現(xiàn)過程中,也要考慮其對(duì)象釋放,最好的方法是在不使用某對(duì)象時(shí),顯式地將此對(duì)象賦值為 null,比如使用完Bitmap 后先調(diào)用 recycle(),再賦為null,清空對(duì)圖片等資源有直接引用或者間接引用的數(shù)組(使用 array.clear() ; array = null)等,最好遵循誰創(chuàng)建誰釋放的原則。 
正確關(guān)閉資源,對(duì)于使用了BraodcastReceiver,ContentObserver,File,游標(biāo) Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷。 
保持對(duì)對(duì)象生命周期的敏感,特別注意單例、靜態(tài)對(duì)象、全局性集合等的生命周期。
?
程序出現(xiàn)停止運(yùn)行狀態(tài)或者致命崩潰現(xiàn)象可能如下原因?qū)е?#xff1a;
1.在while死循環(huán)里面或軟件操作非常頻繁的代碼塊中進(jìn)行new對(duì)象產(chǎn)生強(qiáng)引用對(duì)象,
導(dǎo)致jvm不能及時(shí)回收內(nèi)存,從而產(chǎn)生內(nèi)存消耗暴增,讓軟件出現(xiàn)停止運(yùn)行的致命崩潰現(xiàn)象
2.代碼報(bào)錯(cuò)未捕捉異常造成
3.程序在主線程耗時(shí)過長(zhǎng),或者在廣播中的耗時(shí)超過6s
?
new出來的對(duì)象不用時(shí)回收原則;
1.在哪創(chuàng)建就在哪及時(shí)釋放,
2.誰引用,誰就負(fù)責(zé)釋放
能做到C/C++對(duì)于程序的“誰創(chuàng)建,誰釋放”原則,那我們對(duì)于內(nèi)存的把握,并不比Java或Android本身的GC機(jī)制差,而且更好的控制內(nèi)存,能使我們的手機(jī)運(yùn)行得更流暢
java沒有絕對(duì)的強(qiáng)制垃圾回收的方法,不過可以這樣去做:
1. 對(duì)于不再引用的對(duì)象,及時(shí)把它的引用賦為null。 obj = null;
2. 如果內(nèi)存確實(shí)很緊張,調(diào)用System.gc() 方法來建議垃圾回收器開始回收垃圾。
?
參考文章
以上部分圖片、實(shí)例代碼和文段都摘自或參考以下文章 :?
IBM :?
Java的內(nèi)存泄漏
Android Design Patterns :?
How to Leak a Context: Handlers & Inner Classes
伯樂在線團(tuán)隊(duì):?
Android性能優(yōu)化之常見的內(nèi)存泄漏
我廠同學(xué) :?
Dalvik虛擬機(jī) Finalize 方法執(zhí)行分析
騰訊bugly :?
內(nèi)存泄露從入門到精通三部曲之基礎(chǔ)知識(shí)篇
LeakCanary :?
LeakCanary 中文使用說明?
LeakCanary: 讓內(nèi)存泄露無所遁形
https://github.com/square/leakcanary
?
轉(zhuǎn)載于:https://www.cnblogs.com/SZ2015/p/6884332.html
總結(jié)
以上是生活随笔為你收集整理的JVM内存管理概述与android内存泄露分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 2015年07月04日
- 下一篇: PHP中的运算符
