一.內存劃分php
將內存劃分爲六大部分,分別是PC寄存器、JAVA虛擬機棧、JAVA堆、方法區、運行時常量池以及本地方法棧.
一、PC寄存器(線程獨有):全稱是程序計數寄存器,它記載着每個線程當前運行的JAVA方法的地址,
若是是當前執行的是本地方法,則程序計數器會是一個空地址。它的做用就是用來支持多線程,線程的阻塞、恢復、
掛起等一系列操做,直觀的想象一下,要是沒有記住每一個線程當前運行的位置,又如何恢復呢。依據這一點,
每個線程都有一個PC寄存器,也就是說PC寄存器是線程獨有的。html
二、JAVA虛擬機棧(線程獨有):JAVA虛擬機棧是在建立線程的同時建立的,用於存儲棧幀,
JAVA虛擬機棧也是線程獨有的。java
三、JAVA堆(全局共享):這一部分是JAVA內存中最重要的一部分,之因此說是最重要的一部分,
並非由於它的重要性,而是指做爲開發人員最應該關注的一部分。它隨着JAVA虛擬機的啓動建立,
儲存着全部對象實例以及數組對象,並且內置了「自動內存管理系統」,也就是咱們常說的垃圾蒐集器(GC)。
JAVA堆中的內存釋放是不受開發人員控制的,徹底由JAVA虛擬機一手操辦。對於JAVA虛擬機如何實現垃圾蒐集器,
JAVA虛擬機規範沒有明確的規定,也正因如此,咱們平時使用的JAVA虛擬機中提供了許多種垃圾蒐集器,
它們採用不一樣的算法以及實現方式,已知足多方面的性能需求。android
四、方法區(全局共享):方法區也是堆的一個組成部分,它主要存儲的是運行時常量池、字段信息、方法信息、
構造方法與普通函數的字節碼內容以及一些特殊方法。它與JAVA堆的區別除了存儲的信息與JAVA堆不同以外,
最大的區別就是這一部分JAVA虛擬機規範不強制要求實現自動內存管理系統(GC)。git
五、本地方法棧(線程獨有):本地方法棧是一個傳統的棧,它用來支持native方法的執行。
若是JAVA虛擬機是使用的其它語言實現指令集解釋器的時候,也會用到本地方法棧。若是前面這兩種都未發生,
也就是說若是JAVA虛擬機不依賴於本地方法棧,並且JAVA虛擬機也不支持native方法,則不須要本地方法棧。
而若是須要的話,則本地方法棧也是隨每個線程的啓動而建立的。程序員
上面五個內存區域,除了PC寄存器以外,其他四個通常狀況下,都要求JAVA虛擬機實現提供給客戶調節大小的參數,
也就是咱們經常使用的Xms、Xmx等等。github
Java 程序運行時的內存分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種存儲策略使用的內存空間主要分別是靜態存儲區(也稱方法區)、棧區和堆區。web
靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,而且在程序整個運行期間都存在。
棧區 :當方法被執行時,方法體內的局部變量都在棧上建立,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。由於棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。
堆區 : 又稱動態內存分配,一般就是指在程序運行時直接 new 出來的內存。這部份內存在不使用時將會由 Java 垃圾回收器來負責回收。
棧與堆的區別:算法
在方法體內定義的(局部變量)一些基本類型的變量和對象的引用變量都是在方法的棧內存中分配的。當在一段方法塊中定義一個變量時,Java 就會在棧中爲該變量分配內存空間,當超過該變量的做用域後,該變量也就無效了,分配給它的內存空間也將被釋放掉,該內存空間能夠被從新使用。編程
堆內存用來存放全部由 new 建立的對象(包括該對象其中的全部成員變量)和數組。在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,這個特殊的變量就是咱們上面說的引用變量。咱們能夠經過這個引用變量來訪問堆中的對象或者數組。
強引用(StrongReference):JVM 寧肯拋出 OOM ,也不會讓 GC 回收具備強引用的對象;
軟引用(SoftReference):只有在內存空間不足時,纔會被回的對象;
弱引用(WeakReference):在 GC 時,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存;
虛引用(PhantomReference):任什麼時候候均可以被GC回收,當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。程序能夠經過判斷引用隊列中是否存在該對象的虛引用,來了解這個對象是否將要被回收。能夠用來做爲GC回收Object的標誌。
咱們常說的內存泄漏是指new出來的Object沒法被GC回收,即爲強引用:
對於C++來講,內存泄漏就是new出來的對象沒有delete,俗稱野指針;對於Java來講,就是new出來的Object 放在Heap上沒法被GC回收;
在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特色,首先,這些對象是可達的,即在有向圖中,存在通路能夠與其相連;其次,這些對象是無用的,即程序之後不會再使用這些對象。若是對象知足這兩個條件,這些對象就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。
在C++中,內存泄漏的範圍更大一些。有些對象被分配了內存空間,而後卻不可達,因爲C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,所以程序員不須要考慮這部分的內存泄露。
經過分析,咱們得知,對於C++,程序員須要本身管理邊和頂點,而對於Java程序員只須要管理邊就能夠了(不須要管理頂點的釋放)。經過這種方式,Java提升了編程的效率。
所以,經過以上分析,咱們知道在Java中也有內存泄漏,但範圍比C++要小一些。由於Java從語言上保證,任何對象都是可達的,全部的不可達對象都由GC管理。
對於程序員來講,GC基本是透明的,不可見的。雖然,咱們只有幾個函數能夠訪問GC,例如運行GC的函數System.gc(),可是根據Java語言規範定義, 該函數不保證JVM的垃圾收集器必定會執行。由於,不一樣的JVM實現者可能使用不一樣的算法管理GC。一般,GC的線程的優先級別較低。JVM調用GC的策略也有不少種,有的是內存使用到達必定程度時,GC纔開始工做,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但一般來講,咱們不須要關心這些。除非在一些特定的場合,GC的執行影響應用程序的性能,例如對於基於Web的實時系統,如網絡遊戲等,用戶不但願GC忽然中斷應用程序執行而進行垃圾回收,那麼咱們須要調整GC的參數,讓GC可以經過平緩的方式釋放內存,例如將垃圾回收分解爲一系列的小步驟執行,Sun提供的HotSpot JVM就支持這一特性。
一樣給出一個 Java 內存泄漏的典型例子,
Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }
在這個例子中,咱們循環申請Object對象,並將所申請的對象放入一個 Vector 中,若是咱們僅僅釋放引用自己,那麼 Vector 仍然引用該對象,因此這個對象對 GC 來講是不可回收的。所以,若是對象加入到Vector 後,還必須從 Vector 中刪除,最簡單的方法就是將 Vector 對象設置爲 null。
什麼是內存溢出:(Out Of Memory,簡稱 OOM),通俗理解就是內存不夠,即內存佔用超出內存的空間大小;基本上會形成程序奔潰。解決:找到報錯的代碼並解決。
什麼是內存泄露:內存泄漏(Memory Leak),簡單理解就是內存使用完畢以後本該垃圾回收卻未被回收。(佔着茅坑不拉屎)--內存沒法回收又沒起做用。
推理!!!
1.看對象的大小;
2.深刻使用MAT進行對象細節的分析
3.先後對比。
懷疑某一段代碼會形成內存溢出。能夠先註冊掉該段代碼生成一個xxx.hroph文件,而後在解開這段代碼再生成一個yyy.hroph文件
經過MAT工具對比他們的內存消耗狀況---甚至能夠精確到每個對象消耗內存的狀況。
在 Android 中,泄露 Context 對象的問題尤爲嚴重,特別像 Activity 這樣的 Context 對象會引用大量很佔用內存的對象,若是 Context 對象發生了內存泄漏,那它所引用的全部對象都被泄漏了。Activity 是很是重量級的對象,因此咱們應該極力避免妨礙系統對其進行回收,然而實際狀況是有多種方式會無心間就泄露了Activity 對象。
case 0.靜態變量形成的內存泄漏
最簡單的泄漏 Activity 就是在 Activity 類中定義一個 static 變量,並將其指向一個運行中的 Activity 實例。若是在 Activity 的生命週期結束以前,沒有清除這個引用,那它就會泄漏。因爲 Activity 的類對象是靜態的,一旦加載,就會在 APP 運行時一直常駐內存,若是類對象不卸載,其靜態成員就不會被垃圾回收。
儘可能避免使用 static 成員變量:
這裏修復的方法是:
不要在類初始時初始化靜態成員。能夠考慮lazy初始化。
單例的靜態特性致使其生命週期同應用同樣長
另外一種相似的狀況是對常常啓動的 Activity 實現一個單例模式,讓其常駐內存可使它可以快速恢復狀態。
如咱們有一個建立起來很是耗時的 View,在同一個 Activity 不一樣的生命週期中都保持不變呢,就爲它實現一個單例模式。一旦 View 被加載到界面中,它就會持有 Context 的強引用,也就是咱們的 Activity 對象。
因爲咱們是經過一個靜態成員引用了這個 View,因此咱們也就引用了 Activity,所以 Activity 就發生了泄漏。因此必定不要把加載的 View 賦值給靜態變量,若是你真的須要,那必定要確保在 Activity 銷燬以前將其從 View 層級中移除。
解決方案:
將該屬性的引用方式改成弱引用;
若是傳入Context,使用ApplicationContext;
咱們常常在 Activity 內部定義一個內部類,這樣作能夠增長封裝性和可讀性。可是若是當咱們建立了一個內部類的對象,並經過靜態變量持有了 Activity 的引用,那也會可能發生 Activity 泄漏。
在Java中,非靜態內部類 和 匿名類 都會潛在的引用它們所屬的外部類,可是,靜態內部類卻不會。若是這個非靜態內部類實例作了一些耗時的操做,就會形成外圍對象不會被回收,從而致使內存泄漏。
解決方案:
將內部類變成靜態內部類;
靜態內部類中使用弱引用來引用外部類的成員變量;
若是有強引用Activity中的屬性,則將該屬性的引用方式改成弱引用;
在業務容許的狀況下,當Activity執行onDestory時,結束這些耗時任務;
在 Activity 內定義了一個匿名的 AsyncTask 對象,就有可能發生內存泄漏。若是 Activity 被銷燬以後 AsyncTask 仍然在執行,那就會阻止垃圾回收器回收Activity 對象,進而致使內存泄漏,直到執行結束才能回收 Activity。
一樣的,使用 Thread 和 TimerTask 也可能致使 Activity 泄漏。只要它們是經過匿名類建立的,儘管它們在單獨的線程被執行,它們也會持有對 Activity 的強引用,進而致使內存泄漏。
在Android裏面線程最容易形成內存泄露。線程產生內存泄露的主要緣由在於線程生命週期的不可控
2.線程問題的改進方式主要有:
1)將線程的內部類,改成靜態內部類。
2)在程序中儘可能採用弱引用保存Context。
在Android應用程序中一般可使用兩種Context對象:Activity和Application。當類或方法須要Context對象的時候常見的作法是使用第一個做爲Context參數。這樣就意味着View對象對整個Activity保持引用,所以也就保持對Activty的全部的引用。
假設一個場景,當應用程序有個比較大的Bitmap類型的圖片,每次旋轉是都從新加載圖片所用的時間較多。爲了提升屏幕旋轉是Activity的建立速度,最簡單的方法時將這個Bitmap對象使用Static修飾。 當一個Drawable綁定在View上,實際上這個View對象就會成爲這份Drawable的一個Callback成員變量。而靜態變量的生命週期要長於Activity。致使了當旋轉屏幕時,Activity沒法被回收,而形成內存泄露。
解決方案:
使用ApplicationContext代替ActivityContext,由於ApplicationContext會隨着應用程序的存在而存在,而不依賴於activity的生命週期;
對Context的引用不要超過它自己的生命週期,慎重的對Context使用「static」關鍵字。Context裏若是有線程,必定要在onDestroy()裏及時停掉。
定義一個匿名的 Runnable 對象並將其提交到 Handler 上也可能致使 Activity 泄漏。Runnable 對象間接地引用了定義它的 Activity 對象,而它會被提交到Handler 的 MessageQueue 中,若是它在 Activity 銷燬時尚未被處理,就會致使 Activity 泄漏。
當Handler中有延遲的的任務或是等待執行的任務隊列過長,因爲消息持有對Handler的引用,而Handler又持有對其外部類的潛在引用,這條引用關係會一直保持到消息獲得處理,而致使了Activity沒法被垃圾回收器回收,而致使了內存泄露。
修復方法:在 Activity 中避免使用非靜態內部類,好比上面咱們將 Handler 聲明爲靜態的,則其存活期跟 Activity 的生命週期就無關了。同時經過弱引用的方式引入 Activity,避免直接將 Activity 做爲 context 傳進去,見下面代碼:
Handler 的持有的引用對象最好使用弱引用,資源釋放時也能夠清空 Handler 裏面的消息。好比在 Activity onStop 或者 onDestroy 的時候,取消掉該 Handler 對象的 Message和 Runnable.
綜述,即推薦使用靜態內部類 + WeakReference 這種方式。每次使用前注意判空。
解決方案:
能夠把Handler類放在單獨的類文件中,或者使用靜態內部類即可以免泄露;
若是想在Handler內部去調用所在的Activity,那麼能夠在handler內部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式來達到斷開Handler與Activity之間存在引用關係的目的。
如系統服務能夠經過 context.getSystemService 獲取,它們負責執行某些後臺任務,或者爲硬件訪問提供接口。若是 Context 對象想要在服務內部的事件發生時被通知,那就須要把本身註冊到服務的監聽器中。然而,這會讓服務持有 Activity 的引用,若是開發者忘記在 Activity 銷燬時取消註冊,也會致使 Activity泄漏
系統服務能夠經過Context.getSystemService 獲取,它們負責執行某些後臺任務,或者爲硬件訪問提供接口。若是Context 對象想要在服務內部的事件發生時被通知,那就須要把本身註冊到服務的監聽器中。然而,這會讓服務持有Activity 的引用,若是在Activity onDestory時沒有釋放掉引用就會內存泄漏。
解決方案:
使用ApplicationContext代替ActivityContext;
在Activity執行onDestory時,調用反註冊;
資源性對象好比(Cursor,File文件等)每每都用了一些緩衝,咱們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收內存。它們的緩衝不只存在於 java虛擬機內,還存在於java虛擬機外。若是咱們僅僅是把它的引用設置爲null,而不關閉它們,每每會形成內存泄漏。由於有些資源性對象,好比SQLiteCursor(在析構函數finalize(),若是咱們沒有關閉它,它本身會調close()關閉),若是咱們沒有關閉它,系統在回收它時也會關閉它,可是這樣的效率過低了。所以對於資源性對象在不使用的時候,應該調用它的close()函數,將其關閉掉,而後才置爲null. 在咱們的程序退出時必定要確保咱們的資源性對象已經關閉。
對於使用了BraodcastReceiver,ContentObserver,File,遊標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,不然這些資源將不會被回收,形成內存泄漏。
Solution:
調用onRecycled()
咱們一般把一些對象的引用加入到了集合容器(好比ArrayList)中,當咱們不須要該對象時,並無把它的引用從集合中清理掉,這樣這個集合就會愈來愈大。若是這個集合是static的話,那狀況就更嚴重了。
因此要在退出程序以前,將集合裏的東西clear,而後置爲null,再退出程序。
解決方案:
在Activity退出以前,將集合裏的東西clear,而後置爲null,再退出程序。
Solution
private List<EmotionPanelInfo> data;
public void onDestory() {
if (data != null) { data.clear(); data = null; } }
當咱們不要使用WebView對象時,應該調用它的destory()函數來銷燬它,並釋放其佔用的內存,不然其佔用的內存長期也不能被回收,從而形成內存泄露。
解決方案:
爲webView開啓另一個進程,經過AIDL與主線程進行通訊,WebView所在的進程能夠根據業務的須要選擇合適的時機進行銷燬,從而達到內存的完整釋放。
初始時ListView會從Adapter中根據當前的屏幕布局實例化必定數量的View對象,同時ListView會將這些View對象 緩存起來。
當向上滾動ListView時,原先位於最上面的List Item的View對象會被回收,而後被用來構造新出現的最下面的List Item。
這個構造過程就是由getView()方法完成的,getView()的第二個形參View ConvertView就是被緩存起來的List Item的View對象(初始化時緩存中沒有View對象則ConvertView是null)。
case 11.動畫
在屬性動畫中有一類無限循環動畫,若是在Activity中播放這類動畫而且在onDestroy中去中止動畫,那麼這個動畫將會一直播放下去,這時候Activity會被View所持有,從而致使Activity沒法被釋放。解決此類問題則是須要早Activity中onDestroy去去調用objectAnimator.cancel()來中止動畫
case 12.第三方庫使用不當
對於EventBus,RxJava等一些第三開源框架的使用,如果在Activity銷燬以前沒有進行解除訂閱將會致使內存泄漏。
綜上所述,要避免內存泄露或者內存溢出,主要要遵循如下幾點:
第一:不要爲Context長期保存引用(要引用Context就要使得引用對象和它自己的生命週期保持一致,即對activity的引用應該控制在activity的生命週期以內)。
第二:若是要使用到Context,儘可能使用ApplicationContext去代替Context,由於ApplicationContext的生命週期較長,引用狀況下不會形成內存泄露問題
第三:在你不控制對象的生命週期的狀況下避免在你的Activity中使用static變量。儘可能使用WeakReference去代替一個static。
第四:垃圾回收器並不保證能準確回收內存,這樣在使用本身須要的內容時,主要生命週期和及時釋放掉不須要的對象。儘可能在Activity的生命週期結束時,在onDestroy中把咱們作引用的其餘對象作資源釋放,好比:cursor.close()。如清空對圖片等資源有直接引用或者間接引用的數組(使用array.clear();array = null);
第五:儘可能不要在Activity中使用非靜態內部類,由於非靜態內部類會隱式持有外部類實例的引用。若是使用靜態內部類,將外部實例引用做爲弱引用持有。
(靜態的內部類不會持有外部類的一個隱式引用)
在 Java 的實現過程當中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦值爲 null,好比使用完Bitmap 後先調用 recycle(),再賦爲null,清空對圖片等資源有直接引用或者間接引用的數組(使用 array.clear() ; array = null)等,最好遵循誰建立誰釋放的原則。
正確關閉資源,對於使用了BraodcastReceiver,ContentObserver,File,遊標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷。
保持對對象生命週期的敏感,特別注意單例、靜態對象、全局性集合等的生命週期。
程序出現中止運行狀態或者致命崩潰現象可能以下緣由致使:
1.在while死循環裏面或軟件操做很是頻繁的代碼塊中進行new對象產生強引用對象,
致使jvm不能及時回收內存,從而產生內存消耗暴增,讓軟件出現中止運行的致命崩潰現象
2.代碼報錯未捕捉異常形成
3.程序在主線程耗時過長,或者在廣播中的耗時超過6s
new出來的對象不用時回收原則;
1.在哪建立就在哪及時釋放,
2.誰引用,誰就負責釋放
能作到C/C++對於程序的「誰建立,誰釋放」原則,那咱們對於內存的把握,並不比Java或Android自己的GC機制差,並且更好的控制內存,能使咱們的手機運行得更流暢
java沒有絕對的強制垃圾回收的方法,不過能夠這樣去作:
1. 對於再也不引用的對象,及時把它的引用賦爲null。 obj = null;
2. 若是內存確實很緊張,調用System.gc() 方法來建議垃圾回收器開始回收垃圾。
以上部分圖片、實例代碼和文段都摘自或參考如下文章 :
IBM :
Java的內存泄漏
Android Design Patterns :
How to Leak a Context: Handlers & Inner Classes
伯樂在線團隊:
Android性能優化之常見的內存泄漏
我廠同窗 :
Dalvik虛擬機 Finalize 方法執行分析
騰訊bugly :
內存泄露從入門到精通三部曲之基礎知識篇
LeakCanary :
LeakCanary 中文使用說明
LeakCanary: 讓內存泄露無所遁形