日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM内存管理概述与android内存泄露分析

發布時間:2024/4/15 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM内存管理概述与android内存泄露分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?一.內存劃分

將內存劃分為六大部分,分別是PC寄存器、JAVA虛擬機棧、JAVA堆、方法區、運行時常量池以及本地方法棧.
1、PC寄存器(線程獨有):全稱是程序計數寄存器,它記載著每一個線程當前運行的JAVA方法的地址,
如果是當前執行的是本地方法,則程序計數器會是一個空地址。它的作用就是用來支持多線程,線程的阻塞、恢復、
掛起等一系列操作,直觀的想象一下,要是沒有記住每個線程當前運行的位置,又如何恢復呢。依據這一點,
每一個線程都有一個PC寄存器,也就是說PC寄存器是線程獨有的。

2、JAVA虛擬機棧(線程獨有):JAVA虛擬機棧是在創建線程的同時創建的,用于存儲棧幀,
JAVA虛擬機棧也是線程獨有的。

3、JAVA堆(全局共享):這一部分是JAVA內存中最重要的一部分,之所以說是最重要的一部分,
并不是因為它的重要性,而是指作為開發人員最應該關注的一部分。它隨著JAVA虛擬機的啟動創建,
儲存著所有對象實例以及數組對象,而且內置了“自動內存管理系統”,也就是我們常說的垃圾搜集器(GC)。
JAVA堆中的內存釋放是不受開發人員控制的,完全由JAVA虛擬機一手操辦。對于JAVA虛擬機如何實現垃圾搜集器,
JAVA虛擬機規范沒有明確的規定,也正因如此,我們平時使用的JAVA虛擬機中提供了許多種垃圾搜集器,
它們采用不同的算法以及實現方式,已滿足多方面的性能需求。

4、方法區(全局共享):方法區也是堆的一個組成部分,它主要存儲的是運行時常量池、字段信息、方法信息、
構造方法與普通函數的字節碼內容以及一些特殊方法。它與JAVA堆的區別除了存儲的信息與JAVA堆不一樣之外,
最大的區別就是這一部分JAVA虛擬機規范不強制要求實現自動內存管理系統(GC)。

5、本地方法棧(線程獨有):本地方法棧是一個傳統的棧,它用來支持native方法的執行。
如果JAVA虛擬機是使用的其它語言實現指令集解釋器的時候,也會用到本地方法棧。如果前面這兩種都未發生,
也就是說如果JAVA虛擬機不依賴于本地方法棧,而且JAVA虛擬機也不支持native方法,則不需要本地方法棧。
而如果需要的話,則本地方法棧也是隨每一個線程的啟動而創建的。

上面五個內存區域,除了PC寄存器之外,其余四個一般情況下,都要求JAVA虛擬機實現提供給客戶調節大小的參數,
也就是我們常用的Xms、Xmx等等。

Java 內存分配策略

Java 程序運行時的內存分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種存儲策略使用的內存空間主要分別是靜態存儲區(也稱方法區)、棧區和堆區。

靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,并且在程序整個運行期間都存在。?
棧區 :當方法被執行時,方法體內的局部變量都在棧上創建,并在方法執行結束時這些局部變量所持有的內存將會自動被釋放。因為棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。?
堆區 : 又稱動態內存分配,通常就是指在程序運行時直接 new 出來的內存。這部分內存在不使用時將會由 Java 垃圾回收器來負責回收。?
棧與堆的區別:

在方法體內定義的(局部變量)一些基本類型的變量和對象的引用變量都是在方法的棧內存中分配的。當在一段方法塊中定義一個變量時,Java 就會在棧中為該變量分配內存空間,當超過該變量的作用域后,該變量也就無效了,分配給它的內存空間也將被釋放掉,該內存空間可以被重新使用。

堆內存用來存放所有由 new 創建的對象(包括該對象其中的所有成員變量)和數組。在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆內存中的首地址,這個特殊的變量就是我們上面說的引用變量。我們可以通過這個引用變量來訪問堆中的對象或者數組。

?

四種引用類型的介紹

  • 強引用(StrongReference):JVM 寧可拋出 OOM ,也不會讓 GC 回收具有強引用的對象;

  • 軟引用(SoftReference):只有在內存空間不足時,才會被回的對象;

  • 弱引用(WeakReference):在 GC 時,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存;

  • 虛引用(PhantomReference):任何時候都可以被GC回收,當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否存在該對象的虛引用,來了解這個對象是否將要被回收。可以用來作為GC回收Object的標志。

  • 我們常說的內存泄漏是指new出來的Object無法被GC回收,即為強引用:  

    什么是Java中的內存泄露

    對于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。

    ?

    Android中常見的內存泄漏匯總?

    什么是內存溢出:(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初始化。

    case 1. 單例造成的內存泄露

    單例的靜態特性導致其生命周期同應用一樣長

    另一種類似的情況是對經常啟動的 Activity 實現一個單例模式,讓其常駐內存可以使它能夠快速恢復狀態。

    ????如我們有一個創建起來非常耗時的 View,在同一個 Activity 不同的生命周期中都保持不變呢,就為它實現一個單例模式。一旦 View 被加載到界面中,它就會持有 Context 的強引用,也就是我們的 Activity 對象。

    ????由于我們是通過一個靜態成員引用了這個 View,所以我們也就引用了 Activity,因此 Activity 就發生了泄漏。所以一定不要把加載的 View 賦值給靜態變量,如果你真的需要,那一定要確保在 Activity 銷毀之前將其從 View 層級中移除。

    解決方案:

  • 將該屬性的引用方式改為弱引用;

  • 如果傳入Context,使用ApplicationContext;

  • case 2. InnerClass匿名內部類

    我們經常在 Activity 內部定義一個內部類,這樣做可以增加封裝性和可讀性。但是如果當我們創建了一個內部類的對象,并通過靜態變量持有了 Activity 的引用,那也會可能發生 Activity 泄漏。

    在Java中,非靜態內部類 和 匿名類 都會潛在的引用它們所屬的外部類,但是,靜態內部類卻不會。如果這個非靜態內部類實例做了一些耗時的操作,就會造成外圍對象不會被回收,從而導致內存泄漏。

    解決方案:

  • 將內部類變成靜態內部類;

  • 靜態內部類中使用弱引用來引用外部類的成員變量;?

  • 如果有強引用Activity中的屬性,則將該屬性的引用方式改為弱引用;

  • 在業務允許的情況下,當Activity執行onDestory時,結束這些耗時任務;

  • case 3. 線程造成的內存泄漏

    ?在 Activity 內定義了一個匿名的 AsyncTask 對象,就有可能發生內存泄漏。如果 Activity 被銷毀之后 AsyncTask 仍然在執行,那就會阻止垃圾回收器回收Activity 對象,進而導致內存泄漏,直到執行結束才能回收 Activity。

    ? ? 同樣的,使用 Thread 和 TimerTask 也可能導致 Activity 泄漏。只要它們是通過匿名類創建的,盡管它們在單獨的線程被執行,它們也會持有對 Activity 的強引用,進而導致內存泄漏。

    在Android里面線程最容易造成內存泄露。線程產生內存泄露的主要原因在于線程生命周期的不可控
    2.線程問題的改進方式主要有:
    ? ?1)將線程的內部類,改為靜態內部類。
    ? ?2)在程序中盡量采用弱引用保存Context。

    case 4. Activity 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()里及時停掉。

  • case 5. Handler引起的內存泄漏

    定義一個匿名的 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之間存在引用關系的目的。

  • case 6. 注冊監聽器的泄漏(資源未關閉造成的內存泄漏)

    如系統服務可以通過 context.getSystemService 獲取,它們負責執行某些后臺任務,或者為硬件訪問提供接口。如果 Context 對象想要在服務內部的事件發生時被通知,那就需要把自己注冊到服務的監聽器中。然而,這會讓服務持有 Activity 的引用,如果開發者忘記在 Activity 銷毀時取消注冊,也會導致 Activity泄漏

    系統服務可以通過Context.getSystemService 獲取,它們負責執行某些后臺任務,或者為硬件訪問提供接口。如果Context 對象想要在服務內部的事件發生時被通知,那就需要把自己注冊到服務的監聽器中。然而,這會讓服務持有Activity 的引用,如果在Activity onDestory時沒有釋放掉引用就會內存泄漏。

    解決方案:

  • 使用ApplicationContext代替ActivityContext;

  • 在Activity執行onDestory時,調用反注冊;

  • case 7. Cursor,Stream沒有close,View沒有recyle

    資源性對象比如(Cursor,File文件等)往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存。它們的緩沖不僅存在于 java虛擬機內,還存在于java虛擬機外。如果我們僅僅是把它的引用設置為null,而不關閉它們,往往會造成內存泄漏。因為有些資源性對象,比如SQLiteCursor(在析構函數finalize(),如果我們沒有關閉它,它自己會調close()關閉),如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。因此對于資源性對象在不使用的時候,應該調用它的close()函數,將其關閉掉,然后才置為null. 在我們的程序退出時一定要確保我們的資源性對象已經關閉。

    對于使用了BraodcastReceiver,ContentObserver,File,游標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存泄漏。

    Solution:

    調用onRecycled()

    case 8. 集合中對象沒清理造成的內存泄漏

    我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當我們不需要該對象時,并沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
    所以要在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。

    解決方案:

    在Activity退出之前,將集合里的東西clear,然后置為null,再退出程序。

    Solution

    private List<EmotionPanelInfo> data; ? ?
    public void onDestory() { ? ? ? ?
    ? ?if (data != null) { ? ? ? ?data.clear(); ? ? ? ?data = null; ? ?} }

    case 9. WebView造成的泄露

    當我們不要使用WebView對象時,應該調用它的destory()函數來銷毀它,并釋放其占用的內存,否則其占用的內存長期也不能被回收,從而造成內存泄露。

    解決方案:

    為webView開啟另外一個進程,通過AIDL與主線程進行通信,WebView所在的進程可以根據業務的需要選擇合適的時機進行銷毀,從而達到內存的完整釋放。

    case 10. 構造Adapter時,沒有使用緩存的ConvertView

    初始時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: 讓內存泄露無所遁形

    https://github.com/square/leakcanary

    ?

    轉載于:https://www.cnblogs.com/SZ2015/p/6884332.html

    總結

    以上是生活随笔為你收集整理的JVM内存管理概述与android内存泄露分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。