面試中最常問的就是:「你瞭解Android內存泄漏和Android內存溢出的緣由嗎,請簡述一下」 ,而後大多數的人都能說出緣由及其例子和解決辦法,可是實際項目中稍微不注意仍是會致使內存泄漏,今天就來梳理一下那些是常見的內存泄漏寫法和解決方法。html
內存泄漏的原理不少人都明白,可是爲了增強你們的防止內存泄漏的意識,我再來講一遍。說到內存泄漏的原理就必需要講一下Java的GC的。Java之因此這麼流行不只僅是他面向對象編程的方式,還有一個重要的緣由是由於,它能幫程序員免去釋放內存的工做,但Java並無咱們想象的那麼智能,它進行內存清理還得依靠固定的判斷邏輯。java
引用計數算法android
給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;在任什麼時候刻計數器的值爲0的對象就是不可能再被使用的,也就是可被回收的對象。這個原理容易理解而且效率很高,可是有一個致命的缺陷就是沒法解決對象之間互相循環引用的問題。以下圖所示程序員
可達性分析算法面試
針對引用計數算法的致命問題,可達性分析算法可以輕鬆的解決這個問題。可達性算法是經過從GC root往外遍歷,若是從root節點沒法遍歷該節點代表該節點對應的對象處於可回收狀態,以下圖中obj一、obj二、obj三、obj5都是能夠從root節點出發所能到達的節點。反觀obj四、obj六、obj7卻沒法從root到達,即便obj六、obj7互相循環引用可是仍是屬於可回收的對象最後被jvm清理。算法
看了這些知識點,咱們再來尋找內存泄漏的緣由,Android是基於Java的一門語言,其垃圾回收機制也是基於Jvm創建的,因此說Android的GC也是經過可達性分析算法來斷定的。可是若是一個存活時間長的對象持有另外一個存活時間短的對象就會致使存活時間短的對象在GC時被認定可達而不能被及時回收也就是咱們常說的內存泄漏。Android對每一個App內存的使用有着嚴格的限制,大量的內存泄漏就可能致使OOM,也就是在new對象請求空間時,堆中沒有剩餘的內存分配所致使的。編程
既然知道了原理那麼平時什麼會出現這種問題和怎麼合理的解決這種問題呢。下面來按實例說話。api
說到Handler這個東西,你們平時確定沒少用這玩意,可是要是用的很差就很是容易出現問題。舉個例子網絡
public Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
toast("handlerLeakcanary");
}
};
private void handlerLeakcanary(){
Message message = new Message();
handler.sendMessageDelayed(message,TIME);
}
複製代碼
老實說寫過代碼的人確定不少。其中不乏瞭解內存泄漏原理的人。可是平時須要多的時候一不當心就可能寫下這氣人的代碼。架構
瞭解Handler機制的人都明白,但message被Handler send出去的時候,會被加入的MessageQueue中,Looper會不停的從MessageQueue中取出Message並分發執行。可是若是Activity 銷燬了,Handler發送的message沒有執行完畢。那麼Handler就不會被回收,可是因爲非靜態內部類默認持有外部類的引用。Handler可達,並持有Activity實例那麼天然jvm就會錯誤的認爲Activity可達不就行GC。這時咱們的Activity就泄漏,Activity做爲App的一個活動頁面其所佔有的內存是不容小視的。那麼怎麼才能合理的解決這個問題呢
一、使用弱引用
Java裏面的引用分爲四種類型強引用、軟引用、弱引用、虛引用。若是有不明白的能夠先去了解一下4種引用的區別。
public static class MyHandler extends Handler{
WeakReference<ResolveLeakcanaryActivity> reference;
public MyHandler(WeakReference<ResolveLeakcanaryActivity> activity){
reference = activity;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (reference.get()!=null){
reference.get().toast("handleMessage");
}
}
}
複製代碼
引用了弱引用就不會打擾到Activity的正常回收。可是在使用以前必定要記得判斷弱引用中包含對象是否爲空,若是爲空則代表代表Activity被回收再也不繼續防止空指針異常
二、使用Handler.removeMessages();
知道緣由就很好解決問題,Handler所致使的Activity內存泄漏正是由於Handler發送的Message任務沒有完成,因此在onDestory中能夠將handler中的message都移除掉,沒有延時任務要處理,activity的生命週期就不會被延長,則能夠正常銷燬。
在Android中單例模式中常常會須要Context對象進行初始化,以下簡單的一段單例代碼示例
public class MyHelper {
private static MyHelper myHelper;
private Context context;
private MyHelper(Context context){
this.context = context;
}
public static synchronized MyHelper getInstance(Context context){
if (myHelper == null){
myHelper = new MyHelper(context);
}
return myHelper;
}
public void doSomeThing(){
}
}
複製代碼
這樣的寫法看起來好像沒啥問題,可是一旦以下調用就會產生內存溢出
public void singleInstanceLeakcanary(){
MyHelper.getInstance(this).doSomeThing();
}
複製代碼
首先單例中有一個static實例,實例持有Activity,可是static變量的生命週期是整個應用的生命週期,確定是會比單個Activity的生命週期長的,因此,當Activity finish時,activity實例被static變量持有不能釋放內存,致使內存泄漏。
解決辦法:
1.使用getApplicationContext()
private void singleInstanceResolve() {
MyHelper.getInstance(getApplicationContext()).doSomeThing();
}
複製代碼
2.改寫單例寫法,在Application裏面進行初始化。
/** * 匿名內部類泄漏包括Handler、Runnable、TimerTask、AsyncTask等 */
public void anonymousClassInstanceLeakcanary(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
複製代碼
這個和Handler內部類致使的異常原理同樣就很少說了。改成靜態內部類+弱引用方式調用就好了。
private static Object inner;
public void innearClassLeakcanary(){
class InnearClass{
}
inner = new InnearClass();
}
複製代碼
由於靜態對象引用了方法內部類,方法內部類也是持有Activity實例的,會致使Activity泄漏
解決方法就是經過在onDestory方法中置空static變量
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://gank.io/api/data/")
.build();
Api mApi = retrofit.create(Api.class);
Call<AndroidBean> androidBeanCall = mApi.getData(20,1);
androidBeanCall.enqueue(new Callback<AndroidBean>() {
@Override
public void onResponse(Call<AndroidBean> call, Response<AndroidBean> response) {
toast("requestLeakcanary");
}
@Override
public void onFailure(Call<AndroidBean> call, Throwable t) {
}
});
複製代碼
這是一段很普通的請求代碼,通常狀況下Wifi請求很快就回調回來了,並不會致使什麼問題,可是若是是在弱網狀況下就會致使接口回來緩慢,這時用戶極可能就會退出Activity不在等待,可是這時網絡請求還未結束,回調接口爲內部類依然會持有Activity的對象,這時Activity就內存泄漏的,而且若是是在Fragment中這樣使用不只會內存泄漏還可能會致使奔潰,以前在公司的時候就是寫了一個Fragment,裏面包含了四個網絡請求,因爲平時操做的時候在Wi-Fi狀況下測試很難發如今這個問題,後面灰度的時候出現Crash,一查才以後當所附屬的Activity已經finish了,可是網絡請求未完成,首先是Fragment內存泄漏,而後調用getResource的時候返回爲null致使異常。這類異常的原理和非靜態內部類相同,因此能夠經過static內部類+弱引用進行處理。因爲本例是經過Retrofit進行,還能夠在onDestory進行call.cancel進行取消任務,也能夠避免內存泄漏。
RxJava最近很火,用的人也多,常常拿來作網絡請求和一些異步任務,可是因爲RxJava的consumer或者是Observer是做爲一個內部類來請求的時候,內存泄漏問題可能又隨之而來
@SuppressLint("CheckResult")
public void rxJavaLeakcanary(){
AppModel.getData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
toast("rxJavaLeakcanary");
}
});
}
複製代碼
這個代碼很常見,可是consumer這個爲內部類,若是異步任務沒有完成Activity依然是存在泄漏的風險的。好在RxJava有取消訂閱的方法可經過以下方法解決
@Override
protected void onDestroy() {
super.onDestroy();
if (disposable!=null && !disposable.isDisposed()){
disposable.dispose();
}
}
複製代碼
看到這個可能有些人會驚訝,爲啥Toast會致使內存泄漏,首先看一下
Toast.makeText(this,"toast",Toast.LENGTH_SHORT);
複製代碼
這個代碼你們都很熟悉吧,可是若是直接這麼作就可能會致使內存泄漏
,這裏傳進去了一個Context,而Toast實際上是在界面上加了一個佈局,Toast裏面有一個LinearLayout,這個Context就是做爲LinearLayout初始化的參數,它會一直持有Activity,你們都知道Toast顯示是有時間限制的,其實也就是一個異步的任務,最後讓其消失,可是若是在Toast還在顯示Activity就銷燬了,因爲Toast顯示沒有結束不會結束生命週期,這個時候Activity就內存泄漏了。
解決方法就是不要直接使用那個代碼,本身封裝一個ToastUtil,使用ApplicationContext來調用。或者經過getApplicationContext來調用,還有一種經過toast變量的cancel來取消這個顯示
private void toast(String msg){
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
複製代碼
看了那麼可能是不是感受其實內存泄漏的原理很簡單,變來變去其實只是形式變了,換湯不換藥。可是在編碼中不注意仍是可能會出現這些問題。瞭解原理以後就去寫代碼吧 😄
主要包括騰訊,以及字節跳動,華爲,小米,等一線互聯網公司主流架構技術。若是你有須要,儘管拿走好了。至於能學會多少,真的只能看你本身
全套體系化高級架構視頻;七大主流技術模塊
部分展現;java內核視頻+源碼+筆記
免費分享
點擊獲取資料文檔;
爲何免費分享?
我不想有不少開發者朋友由於門檻而錯過這套高級架構資料,錯過提高成爲架構師的可能。國內程序員千千萬,大多數是溫水煮青蛙的現狀,靠着每天加班,拿着外人覺得還不錯的薪資待遇。
請記住自身技術水平纔是咱們的核心競爭力,千萬別把年輕和能加班當作本錢。