【知識】メモリ漏洩

6396 ワード

オリジナルは容易ではありませんて、作者を尊重して、転載して出典を明記してください

前言


メモリ漏洩とは、あるオブジェクトが元の回収を使用する必要がなくなった場合、別のオブジェクトが参照を保持して回収できなくなり、そのオブジェクトがスタックメモリにとどまり、メモリ漏洩が発生することを意味します.

メモリ漏洩の影響


メモリの漏洩は、AppにOOMが発生する主な原因です.

メモリ漏洩の検出方法


2つのツール:MAT、LeakCanary
MAT:強力なメモリ分析ツール.
LeakCanary:Squareオープンソースの軽量級サードパーティメモリ漏洩検出工.

一般的なメモリ漏洩および解決策


1、一例によるメモリ漏洩
単一のインスタンスの静的特性により、そのライフサイクルはアプリケーションのライフサイクルと同じ長さになるため、1つのオブジェクトが使用する必要がなくなり、単一のオブジェクトがオブジェクトの参照を持っている場合、オブジェクトが正常に回収されず、メモリが漏洩します.
例:
public class SingleInstanceTest {

    private static SingleInstanceTest sInstance;
    private Context mContext;

    private SingleInstanceTest(Context context){
        this.mContext = context;
    }

    public static SingleInstanceTest newInstance(Context context){
        if(sInstance == null){
            sInstance = new SingleInstanceTest(context);
        }
        return sInstance;
    }
}

Contextを送信する必要があるため、このContextのライフサイクルの長さは重要です.
1).この時点でアプリケーションのContextが入力されている場合、アプリケーションのライフサイクルはアプリケーション全体のライフサイクルであるため、問題はありません.
2).この時点でActivityのContextが入力されている場合、このContextに対応するActivityが終了すると、このContextの参照は単一のオブジェクトによって保持され、そのライフサイクルはアプリケーション全体のライフサイクルに等しいため、現在のActivityが終了するとメモリが回収されず、メモリが漏洩することになります.
解決策:
⑴アプリケーションのContext取得
public class SingleInstanceTest {

    private static SingleInstanceTest sInstance;
    private Context mContext;

    private SingleInstanceTest(Context context){
        this.mContext = context.getApplicationContext();
    }

    public static SingleInstanceTest newInstance(Context context){
        if(sInstance == null){
            sInstance = new SingleInstanceTest(context);
        }
        return sInstance;
    }
}

(2)弱引用の使用方法
public class Sample {

    private WeakReference mWeakReference;

    public Sample(Context context){
        this.mWeakReference = new WeakReference<>(context);
    }

    public Context getContext() {
        if(mWeakReference.get() != null){
            return mWeakReference.get();
        }
        return null;
    }
}

弱いリファレンスに関連付けられたオブジェクトは、次のゴミ回収までしか生きられません.つまり、SampleがActivityのリファレンスを持っていても、GCがリファレンスを回収してくれるため、破棄されたActivityもメモリを回収され、メモリ漏洩の心配はありません.
2、静的でない内部クラス静的インスタンスの作成によるメモリリーク
コントラスト
静的内部クラス
非静的内部クラス
外部class参照関係
パラメータが入力されていない場合は、参照関係はありません.
強い参照を自動的に取得
呼び出し時に外部インスタンスが必要
不要
必要
外部classの変数とメソッドを呼び出すことができますか?
できません
できる
ライフサイクル
自主的なライフサイクル
外部クラスに依存し、外部クラスよりも長い
上記の表から、非静的内部クラスは外部クラスの強い参照を自動的に取得し、その生命は外部クラスよりも長く、activityの非静的内部クラスのライフサイクルがactivityよりも長い場合、activityのメモリは回収されず、空のポインタの問題が発生する可能性があることがわかります.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyAscnyTask().execute();
    }

    class MyAscnyTask extends AsyncTask{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

私たちはactivityでAsyncTaskを継承して非静的内部クラスをカスタマイズし、doInbackgroundメソッドで時間のかかる操作を行い、onCreateでMyAsyncTaskを起動し、時間のかかる操作が終了する前にactivityが破棄されると、MyAsyncTaskがactivityの強い参照を持っているため、activityのメモリが回収できなくなります.
解決策:非静的内部クラスを静的内部クラスに変更する
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyAscnyTask().execute();
    }

    static class MyAscnyTask extends AsyncTask{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(50000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

3、匿名類によるメモリ漏洩
匿名クラスと非静的内部クラスは同じ共通点が外部クラスの参照を持つことであり,匿名クラスによるメモリ漏洩の原因も非静的内部クラスと基本的に同じである.
public class MainActivity extends AppCompatActivity {

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ①   Activity  , 
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(50000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // ②   Handler  
        Message message = Message.obtain();
        mHandler.sendMessageDelayed(message, 60000);
    }

上記のコードの2箇所でメモリ漏洩が発生する可能性がある:1).匿名のthreadが時間のかかる操作を行う場合、MainActivityが破棄され、threadの時間のかかる操作が終了しないとメモリリークが発生します.
2).匿名のhandlerがメッセージを送信する場合、MainActivityが破棄され、handlerのメッセージがまだ送信されていない場合、activityのメモリも回収されません.
解决方法:1).threadを継承して静的内部クラスを実装
2).handlerを継承して静的内部クラスを実現し、activity破棄時にすべてのメッセージmHandlerを除去する.removeCallbacksAndMessages(null);
4、リソースがシャットダウンされていないことによるメモリ漏洩
broadcast、file、cursor、stream、bitmapなどのリソースを使用した後、activity破棄時に閉じたりログアウトしたりしないと、リソースは回収されず、メモリが漏洩します.
解決策:
Activity破棄時にリソースを閉じるかログアウトします.
5、集合クラス操作のメモリ漏洩
コレクションクラスが要素を追加した後も、コレクション要素オブジェクトが参照され、そのコレクション内の要素オブジェクトが回収されず、メモリが漏洩します.
   static List objectList = new ArrayList<>();
   for (int i = 0; i < 10; i++) {
       Object obj = new Object();
       objectList.add(obj);
       obj = null;
    }

この例では、objectオブジェクトは静的集合に格納されます.静的変数のライフサイクルがアプリケーションと一致するため、参照したオブジェクトobjectも解放されず、メモリが漏洩します.
解決策:
コレクション要素の使用後にコレクションから削除し、すべての要素が使用されると、コレクションを空にします.
    objectList.clear();
    objectList = null;

6、WebViewによるメモリ漏洩
Webviewを使用するとメモリが漏洩します
解決策:
プロセスを開き、AIDLを介してプライマリスレッドと通信し、適切なタイミングで破棄します.