成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Android面試被問到內存泄漏了雜整?

開發(fā) 后端 Android
內存泄漏即該被釋放的內存沒有被及時的釋放,一直被某個或某些實例所持有卻不再使用導致GC不能回收。Java程序運行時的內存分配策略有三種,分別是靜態(tài)分配,棧式分配,和堆式分配。對應的三種策略使用的內存空間是要分別是靜態(tài)存儲區(qū)(也稱方法區(qū)),棧區(qū),和堆區(qū)。

[[213752]]

內存泄漏即該被釋放的內存沒有被及時的釋放,一直被某個或某些實例所持有卻不再使用導致GC不能回收。

Java內存分配策略

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

  • 靜態(tài)存儲區(qū)(方法區(qū)):主要存放靜態(tài)數據,全局static數據和常量。這塊內存在程序編譯時就已經分配好,并且在程序整個運行期間都存在。
  • 棧區(qū):當方法執(zhí)行時,方法內部的局部變量都建立在棧內存中,并在方法結束后自動釋放分配的內存。因為棧內存分配是在處理器的指令集當中所以效率很高,但是分配的內存容量有限。
  • 堆區(qū):又稱動態(tài)內存分配,通常就是指在程序運行時直接new出來的內存。這部分內存在不適用時將會由Java垃圾回收器來負責回收。

棧與堆的區(qū)別:

在方法體內定義的(局部變量)一些基本類型的變量和對象的引用變量都在方法的棧內存中分配。當在一段方法塊中定義一個變量時,Java就會在棧中為其分配內存,當超出變量作用域時,該變量也就無效了,此時占用的內存就會釋放,然后會被重新利用。

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

舉個例子:

  1. public class Sample {     
  2.     int s1 = 0; 
  3.     Sample mSample1 = new Sample();    
  4.   
  5.     public void method() {         
  6.         int s2 = 0; 
  7.         Sample mSample2 = new Sample(); 
  8.     } 
  9.     Sample mSample3 = new Sample();  

如上局部變量s2和mSample2存放在棧內存中,mSample3所指向的對象存放在堆內存中,包括該對象的成員變量s1和mSample1也存放在堆中,而它自己則存放在棧中。

結論:

局部變量的基本類型和引用存儲在棧內存中,引用的實體存儲在堆中。——因它們存在于方法中,隨方法的生命周期而結束。

成員變量全部存儲于堆中(包括基本數據類型,引用和引用的對象實體)。——因為它們屬于類,類對象終究要被new出來使用。

了解了Java的內存分配之后,我們再來看看Java是怎么管理內存。

Java是如何管理內存

由程序分配內存,GC來釋放內存。內存釋放的原理為該對象或者數組不再被引用,則JVM會在適當的時候回收內存。

內存管理算法:

    1. 引用計數法:對象內部定義引用變量,當該對象被某個引用變量引用時則計數加1,當對象的某個引用變量超出生命周期或者引用了新的變量時,計數減1。任何引用計數為0的對象實例都可以被GC。這種算法的優(yōu)點是:引用計數收集器可以很快的執(zhí)行,交織在程序運行中。對程序需要不被長時間打斷的實時環(huán)境比較有利。缺點:無法檢測出循環(huán)引用。

引用計數無法解決的循環(huán)引用問題如下:   

  1. public void method() {         
  2.         //Sample count=1 
  3.         Sample ob1 = new Sample();         
  4.         //Sample count=2 
  5.         Sample ob2 = new Sample();         
  6.         //Sample count=3 
  7.         ob1.mSample = ob2;         
  8.         //Sample count=4 
  9.         ob2.mSample = ob1;         
  10.         //Sample count=3 
  11.         ob1=null;         
  12.         //Sample count=2 
  13.         ob2=null;         
  14.         //計數為2,不能被GC 
  15.     }  

Java可以作為GC ROOT的對象有:虛擬機棧中引用的對象(本地變量表),方法區(qū)中靜態(tài)屬性引用的對象,方法區(qū)中常量引用的對象,本地方法棧中引用的對象(Native對象)

    2. 標記清除法:從根節(jié)點集合進行掃描,標記存活的對象,然后再掃描整個空間,對未標記的對象進行回收。在存活對象較多的情況下,效率很高,但是會造成內存碎片。

    3. 標記整理算法:同標記清除法,只不過在回收對象時,對存活的對象進行移動。雖然解決了內存碎片的問題但是增加了內存的開銷。

    4. 復制算法:此方法為克服句柄的開銷和解決堆碎片。把堆分為一個對象面和多個空閑面。把存活的對象copy到空閑面,主要空閑面就變成了對象面,原來的對象面就變成了空閑面。這樣增加了內存的開銷,且在交換過程中程序會暫停執(zhí)行。

    5. 分代算法:分代垃圾回收策略,是基于:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的回收算法,以便提高回收效率。

年輕代:

    1. 所有新生成的對象首先都是存放在年輕代。年輕代的目標就是盡可能快速的收集掉那些生命周期短的對象。

    2. 新生代內存按照8:1:1的比例分為一個eden區(qū)和兩個survivor(survivor0,survivor1)區(qū)。一個Eden區(qū),兩個 Survivor區(qū)(一般而言)。大部分對象在Eden區(qū)中生成。回收時先將eden區(qū)存活對象復制到一個survivor0區(qū),然后清空eden區(qū),當這個survivor0區(qū)也存放滿了時,則將eden區(qū)和survivor0區(qū)存活對象復制到另一個survivor1區(qū),然后清空eden和這個survivor0區(qū),此時survivor0區(qū)是空的,然后將survivor0區(qū)和survivor1區(qū)交換,即保持survivor1區(qū)為空, 如此往復。

    3. 當survivor1區(qū)不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。若是老年代也滿了就會觸發(fā)一次Full GC,也就是新生代、老年代都進行回收

    4. 新生代發(fā)生的GC也叫做Minor GC,MinorGC發(fā)生頻率比較高(不一定等Eden區(qū)滿了才觸發(fā))

年老代:

    1. 在年輕代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。

    2. 內存比新生代也大很多(大概比例是1:2),當老年代內存滿時觸發(fā)Major GC即Full GC,Full GC發(fā)生頻率比較低,老年代對象存活時間比較長,存活率標記高。

持久代:

用于存放靜態(tài)文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態(tài)生成或者調用一些class,例如Hibernate 等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。

Android常見的內存泄漏匯總

集合類泄漏

先看一段代碼   

  1. List<Object> objectList = new ArrayList<>();         
  2.       for (int i = 0; i < 10; i++) { 
  3.            Object o = new Object(); 
  4.            objectList.add(o); 
  5.            o = null
  6.        }  

上面的實例,雖然在循環(huán)中把引用o釋放了,但是它被添加到了objectList中,所以objectList也持有對象的引用,此時該對象是無法被GC的。因此對象如果添加到集合中,還必須從中刪除,最簡單的方法 

  1. //釋放objectList 
  2.         objectList.clear(); 
  3.         objectList=null; 

單例造成的內存泄漏

由于單例的靜態(tài)特性使得其生命周期跟應用的生命周期一樣長,所以如果使用不恰當的話,很容易造成內存泄漏。比如下面一個典型的例子。

  1. public class SingleInstanceClass {     
  2.     private static SingleInstanceClass instance;     
  3.     private Context mContext;     
  4.     private SingleInstanceClass(Context context) {         
  5.         this.mContext = context; 
  6.     }   
  7.    
  8.     public SingleInstanceClass getInstance(Context context) {         
  9.         if (instance == null) { 
  10.             instance = new SingleInstanceClass(context); 
  11.         }         
  12.         return instance; 
  13.     } 

正如前面所說,靜態(tài)變量的生命周期等同于應用的生命周期,此處傳入的Context參數便是禍端。如果傳遞進去的是Activity或者Fragment,由于單例一直持有它們的引用,即便Activity或者Fragment銷毀了,也不會回收其內存。特別是一些龐大的Activity非常容易導致OOM。

正確的寫法應該是傳遞Application的Context,因為Application的生命周期就是整個應用的生命周期,所以沒有任何的問題。 

  1. public class SingleInstanceClass {     
  2.  
  3.     private static SingleInstanceClass instance;     
  4.  
  5.     private Context mContext;     
  6.  
  7.     private SingleInstanceClass(Context context) {         
  8.  
  9.         this.mContext = context.getApplicationContext();// 使用Application 的context 
  10.  
  11.     }      
  12.  
  13.     public SingleInstanceClass getInstance(Context context) {         
  14.  
  15.         if (instance == null) { 
  16.  
  17.             instance = new SingleInstanceClass(context); 
  18.  
  19.         }         
  20.  
  21.         return instance; 
  22.  
  23.     } 
  24.  
  25.  
  26.  
  27.  
  28. or  
  29.          
  30.  
  31. //在Application中定義獲取全局的context的方法 
  32.  
  33.  /** 
  34.  
  35.      * 獲取全局的context 
  36.  
  37.      * @return 返回全局context對象 
  38.  
  39.      */ 
  40.  
  41.     public static Context getContext(){         
  42.  
  43.         return context; 
  44.  
  45.     }  
  46.  
  47.  
  48. public class SingleInstanceClass {     
  49.  
  50.     private static SingleInstanceClass instance;     
  51.  
  52.     private Context mContext;     
  53.  
  54.     private SingleInstanceClass() { 
  55.  
  56.        mContext=MyApplication.getContext; 
  57.  
  58.     }     
  59.  
  60.     public SingleInstanceClass getInstance() {         
  61.  
  62.         if (instance == null) { 
  63.  
  64.             instance = new SingleInstanceClass(); 
  65.  
  66.         }         
  67.  
  68.         return instance; 
  69.  
  70.     } 
  71.  
  72.  

匿名內部類/非靜態(tài)內部類和異步線程

  • 非靜態(tài)內部類創(chuàng)建靜態(tài)實例造成的內存泄漏

我們都知道非靜態(tài)內部類是默認持有外部類的引用的,如果在內部類中定義單例實例,會導致外部類無法釋放。如下面代碼:

  1. public class TestActivity extends AppCompatActivity {     
  2.     public static InnerClass innerClass = null;  
  3.     
  4.     @Override 
  5.     protected void onCreate(@Nullable Bundle savedInstanceState) {         
  6.         super.onCreate(savedInstanceState);         
  7.         if (innerClass == null
  8.             innerClass = new InnerClass(); 
  9.     }     
  10.     private class InnerClass {         
  11.         //... 
  12.     } 
  13.  

當TestActivity銷毀時,因為innerClass生命周期等同于應用生命周期,但是它又持有TestActivity的引用,因此導致內存泄漏。

正確做法應將該內部類設為靜態(tài)內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。當然,Application 的 context 不是***的,所以也不能隨便亂用,對于有些地方則必須使用 Activity 的 Context,對于Application,Service,Activity三者的Context的應用場景如下:

  • 匿名內部類

android開發(fā)經常會繼承實現Activity/Fragment/View,此時如果你使用了匿名類,并被異步線程持有了,那要小心了,如果沒有任何措施這樣一定會導致泄露。如下代碼:

  1. public class TestActivity extends AppCompatActivity {   
  2.     //.... 
  3.  
  4.     private Runnable runnable=new Runnable() {         
  5.         @Override 
  6.         public void run() { 
  7.  
  8.         } 
  9.     };     
  10.     @Override 
  11.     protected void onCreate(@Nullable Bundle savedInstanceState) {         
  12.         super.onCreate(savedInstanceState);        
  13.         //...... 
  14.     } 
  15.  

上面的runnable所引用的匿名內部類持有TestActivity的引用,當將其傳入異步線程中,線程與Activity生命周期不一致就會導致內存泄漏。

  • Handler造成的內存泄漏

Handler造成內存泄漏的根本原因是因為,Handler的生命周期與Activity或者View的生命周期不一致。Handler屬于TLS(Thread Local Storage)生命周期同應用周期一樣。看下面的代碼:

  1. public class TestActivity extends AppCompatActivity {     
  2.  
  3.     private Handler mHandler = new Handler() {         
  4.  
  5.         @Override 
  6.  
  7.         public void dispatchMessage(Message msg) {             
  8.  
  9.             super.dispatchMessage(msg); 
  10.  
  11.         } 
  12.  
  13.     };     
  14.  
  15.     @Override 
  16.  
  17.     protected void onCreate(@Nullable Bundle savedInstanceState) {         
  18.  
  19.         super.onCreate(savedInstanceState); 
  20.  
  21.         mHandler.postDelayed(new Runnable() {             
  22.  
  23.             @Override 
  24.  
  25.             public void run() {             
  26.  
  27.             //do your things 
  28.  
  29.             } 
  30.  
  31.         }, 60 * 1000 * 10); 
  32.  
  33.         finish(); 
  34.  
  35.     } 
  36.  
  37.  

在該TestActivity中聲明了一個延遲10分鐘執(zhí)行的消息 Message,mHandler將其 push 進了消息隊列 MessageQueue 里。當該 Activity 被finish()掉時,延遲執(zhí)行任務的Message 還會繼續(xù)存在于主線程中,它持有該 Activity 的Handler引用,所以此時 finish()掉的 Activity 就不會被回收了從而造成內存泄漏(因 Handler 為非靜態(tài)內部類,它會持有外部類的引用,在這里就是指TestActivity)。

修復方法:采用內部靜態(tài)類以及弱引用方案。代碼如下:

  1. public class TestActivity extends AppCompatActivity {     
  2.  
  3.     private MyHandler mHandler;     
  4.  
  5.     private static class MyHandler extends Handler {         
  6.  
  7.         private final WeakReference<TestActivity> mActivity;         
  8.  
  9.         public MyHandler(TestActivity activity) { 
  10.  
  11.             mActivity = new WeakReference<>(activity); 
  12.  
  13.         }      
  14.  
  15.     
  16.  
  17.         @Override 
  18.  
  19.         public void dispatchMessage(Message msg) {             
  20.  
  21.             super.dispatchMessage(msg); 
  22.  
  23.             TestActivity activity = mActivity.get();             
  24.  
  25.             //do your things 
  26.  
  27.         } 
  28.  
  29.     }     
  30.  
  31. private static final Runnable mRunnable = new Runnable() {         
  32.  
  33.         @Override 
  34.  
  35.         public void run() {             
  36.  
  37.             //do your things 
  38.  
  39.         } 
  40.  
  41.     };     
  42.  
  43.     @Override 
  44.  
  45.     protected void onCreate(@Nullable Bundle savedInstanceState) {         
  46.  
  47.         super.onCreate(savedInstanceState); 
  48.  
  49.         mHandler = new MyHandler(this); 
  50.  
  51.         mHandler.postAtTime(mRunnable, 1000 * 60 * 10); 
  52.  
  53.         finish(); 
  54.  
  55.     } 
  56.  
  57.  

需要注意的是:使用靜態(tài)內部類 + WeakReference 這種方式,每次使用前注意判空。

前面提到了 WeakReference,所以這里就簡單的說一下 Java 對象的幾種引用類型。

Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。

ok,繼續(xù)回到主題。前面所說的,創(chuàng)建一個靜態(tài)Handler內部類,然后對 Handler 持有的對象使用弱引用,這樣在回收時也可以回收 Handler 持有的對象,但是這樣做雖然避免了Activity泄漏,不過Looper 線程的消息隊列中還是可能會有待處理的消息,所以我們在Activity的 Destroy 時或者 Stop 時應該移除消息隊列 MessageQueue 中的消息。

下面幾個方法都可以移除 Message:

  1. public final void removeCallbacks(Runnable r); 
  2.  
  3. public final void removeCallbacks(Runnable r, Object token); 
  4.  
  5. public final void removeCallbacksAndMessages(Object token); 
  6.  
  7. public final void removeMessages(int what); 
  8.  
  9. public final void removeMessages(int what, Object object);  

盡量避免使用 staic 成員變量

如果成員變量被聲明為 static,那我們都知道其生命周期將與整個app進程生命周期一樣。

這會導致一系列問題,如果你的app進程設計上是長駐內存的,那即使app切到后臺,這部分內存也不會被釋放。按照現在手機app內存管理機制,占內存較大的后臺進程將優(yōu)先回收,意味著如果此app做過進程互保保活,那會造成app在后臺頻繁重啟。就會出現一夜時間手機被消耗空了電量、流量,這樣只會被用戶棄用。

這里修復的方法是:

不要在類初始時初始化靜態(tài)成員。可以考慮lazy初始化。

架構設計上要思考是否真的有必要這樣做,盡量避免。如果架構需要這么設計,那么此對象的生命周期你有責任管理起來。

  • 避免 override finalize():
  1. finalize 方法被執(zhí)行的時間不確定,不能依賴與它來釋放緊缺的資源。時間不確定的原因是: 虛擬機調用GC的時間不確定以及Finalize daemon線程被調度到的時間不確定。
  2. finalize 方法只會被執(zhí)行一次,即使對象被復活,如果已經執(zhí)行過了 finalize 方法,再次被 GC 時也不會再執(zhí)行了,原因是:含有 finalize 方法的 object 是在 new 的時候由虛擬機生成了一個 finalize reference 在來引用到該Object的,而在 finalize 方法執(zhí)行的時候,該 object 所對應的 finalize Reference 會被釋放掉,即使在這個時候把該 object 復活(即用強引用引用住該 object ),再第二次被 GC 的時候由于沒有了 finalize reference 與之對應,所以 finalize 方法不會再執(zhí)行。
  3. 含有Finalize方法的object需要至少經過兩輪GC才有可能被釋放。

其它 

內存泄漏檢測工具強烈推薦 squareup 的 LeakCannary,但需要注意Android版本是4.4+的,否則會Crash。 

責任編輯:龐桂玉 來源: 安卓巴士Android開發(fā)者門戶
相關推薦

2020-09-22 07:49:05

內存泄漏

2018-10-31 12:41:11

2024-03-06 08:00:56

javaAQS原生

2023-04-10 09:32:00

DubboJava

2020-10-10 09:01:54

泄漏

2020-09-15 10:25:13

Redis命令Java

2023-10-31 16:40:38

LeakCanary內存泄漏

2016-03-21 10:31:25

Android內存泄露

2016-12-22 17:21:11

Android性能優(yōu)化內存泄漏

2021-04-30 08:21:22

Linux管道設計

2013-08-07 10:16:43

Android內存泄漏

2016-07-05 14:09:02

AndroidJAVA內存

2011-06-01 12:50:41

Android 內存

2014-07-30 14:22:41

AndroidWebView內存泄漏

2020-04-30 10:24:35

Spring循環(huán)依賴Java

2019-11-29 10:16:36

高并發(fā)系統(tǒng)緩存

2023-11-01 16:50:58

2024-07-03 11:28:15

2013-02-20 16:02:02

Android開發(fā)內存泄露

2013-08-02 09:52:14

AndroidApp內存泄漏
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天天射色综合 | 国产成人一区在线 | 九九热在线视频观看这里只有精品 | 日本电影网站 | 国产精品永久免费视频 | 精产国产伦理一二三区 | 国产三区精品 | 色黄爽 | 国产精品国产自产拍高清 | 波多野结衣av中文字幕 | 麻豆av电影网 | 日皮视频免费 | 日日摸天天添天天添破 | www.黄色网 | 成人精品一区亚洲午夜久久久 | 欧美一级高潮片免费的 | 亚洲精品乱码久久久久久蜜桃91 | 国产精品一区二 | 国产欧美在线播放 | 欧美日韩一区二区三区四区五区 | 久久精品中文 | 国产精品美女久久久久久久网站 | 午夜精品导航 | 91超碰在线 | 精品亚洲永久免费精品 | 亚洲精品在线视频 | www.亚洲视频| 国产高清视频在线观看 | 国产日韩视频 | 国产精品久久久久久久久婷婷 | 色综合国产| 国产精品久久一区二区三区 | 久久久久久久久久一区二区 | 欧美另类视频 | 久久久久久一区 | 久久久久国产一区二区三区四区 | 夜夜操av | 亚洲精品一区二区三区在线 | 一区二区三区四区不卡视频 | 国产人免费人成免费视频 | 在线免费观看毛片 |