偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Android面試被問到內(nèi)存泄漏了雜整?

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

[[213752]]

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

Java內(nèi)存分配策略

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

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

棧與堆的區(qū)別:

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

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

舉個例子:

  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存放在棧內(nèi)存中,mSample3所指向的對象存放在堆內(nèi)存中,包括該對象的成員變量s1和mSample1也存放在堆中,而它自己則存放在棧中。

結(jié)論:

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

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

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

Java是如何管理內(nèi)存

由程序分配內(nèi)存,GC來釋放內(nèi)存。內(nèi)存釋放的原理為該對象或者數(shù)組不再被引用,則JVM會在適當?shù)臅r候回收內(nèi)存。

內(nèi)存管理算法:

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

引用計數(shù)無法解決的循環(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.         //計數(shù)為2,不能被GC 
  15.     }  

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

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

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

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

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

年輕代:

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

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

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

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

年老代:

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

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

持久代:

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

Android常見的內(nèi)存泄漏匯總

集合類泄漏

先看一段代碼   

  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; 

單例造成的內(nèi)存泄漏

由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長,所以如果使用不恰當?shù)脑挘苋菀自斐蓛?nè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)變量的生命周期等同于應(yīng)用的生命周期,此處傳入的Context參數(shù)便是禍端。如果傳遞進去的是Activity或者Fragment,由于單例一直持有它們的引用,即便Activity或者Fragment銷毀了,也不會回收其內(nèi)存。特別是一些龐大的Activity非常容易導(dǎo)致OOM。

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

  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.  

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

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

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

  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生命周期等同于應(yīng)用生命周期,但是它又持有TestActivity的引用,因此導(dǎo)致內(nèi)存泄漏。

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

  • 匿名內(nèi)部類

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

  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所引用的匿名內(nèi)部類持有TestActivity的引用,當將其傳入異步線程中,線程與Activity生命周期不一致就會導(dǎo)致內(nèi)存泄漏。

  • Handler造成的內(nèi)存泄漏

Handler造成內(nèi)存泄漏的根本原因是因為,Handler的生命周期與Activity或者View的生命周期不一致。Handler屬于TLS(Thread Local Storage)生命周期同應(yīng)用周期一樣??聪旅娴拇a:

  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í)行任務(wù)的Message 還會繼續(xù)存在于主線程中,它持有該 Activity 的Handler引用,所以此時 finish()掉的 Activity 就不會被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類,它會持有外部類的引用,在這里就是指TestActivity)。

修復(fù)方法:采用內(nèi)部靜態(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)內(nèi)部類 + WeakReference 這種方式,每次使用前注意判空。

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

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

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

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

這里修復(fù)的方法是:

不要在類初始時初始化靜態(tài)成員??梢钥紤]lazy初始化。

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

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

其它 

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

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

2020-09-22 07:49:05

內(nèi)存泄漏

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內(nèi)存泄漏

2016-03-21 10:31:25

Android內(nèi)存泄露

2016-12-22 17:21:11

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

2021-04-30 08:21:22

Linux管道設(shè)計

2013-08-07 10:16:43

Android內(nèi)存泄漏

2016-07-05 14:09:02

AndroidJAVA內(nèi)存

2011-06-01 12:50:41

Android 內(nèi)存

2014-07-30 14:22:41

AndroidWebView內(nèi)存泄漏

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ā)內(nèi)存泄露

2013-08-02 09:52:14

AndroidApp內(nèi)存泄漏
點贊
收藏

51CTO技術(shù)棧公眾號