English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Perdita di memoria, se non si presta attenzione in Android, è abbastanza facile verificarsi, specialmente nell'Activity, è piuttosto facile verificarsi, quindi vi spiego come ho trovato la perdita di memoria.
Prima di tutto, cos'è una perdita di memoria?
Le perdite di memoria sono alcuni oggetti che non vengono utilizzati ancora esistono nella memoria e il meccanismo di raccolta dei rifiuti non può recuperarli, causando che rimangano nella memoria, facendo crescere sempre di più l'uso della memoria, portando alla diminuzione delle prestazioni del programma.
In Android Virtual Machine, viene utilizzato un algoritmo di ricerca di root per enumerare i nodi radice e determinare se sono spazzatura. Il virtual machine partirà dal GC Roots e attraverserà, se un nodo non può trovare una via per il GC Roots, ossia non è connesso ai GC Roots, allora dimostra che il riferimento è invalido e può essere recuperato. Le perdite di memoria sono alcune pessime chiamate che causano che alcuni oggetti inutilizzati siano connessi ai GC Roots, e non possono essere recuperati.
Dopo aver capito cosa sono le perdite di memoria, naturalmente sappiamo come evitarle, è nel nostro scrittura del codice per cercare di evitare che i riferimenti a oggetti inutilizzati siano mantenuti a lungo. Sembra semplice, ma è necessario avere abbastanza esperienza per raggiungere questo obiettivo, quindi le perdite di memoria sono piuttosto facili da verificare. Poiché non è facile evitarle completamente, dobbiamo essere in grado di trovare le perdite di memoria nel programma e correggerle.
Ora spiegherò come trovare le perdite di memoria.
Ricerca di perdite di memoria:
Per esempio, il seguente codice:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String string = new String(); } public void click(View view){ Intent intent = new Intent(); intent.setClass(getApplicationContext(), SecondActivity.class); startActivity(intent); } }
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } }
Ogni volta che si salta a questa Activity, viene chiamato un thread, poi questo thread esegue il metodo run di runnable. Poiché Runnable è un oggetto interno anonimo, ha una riferimento a SecondActivity, quindi in modo molto semplice, due Activity possono essere saltate da MainActivity a SecondActivity. Di seguito saltiamo da MainActivity a SecondActivity, quindi torniamo da SecondActivity a MainActivity, facciamo questo 5 volte consecutive, infine torniamo a MainActivity. Sulla base della logica, dopo che torniamo da SecondActivity a MainActivity, SecondActivity dovrebbe essere distrutta e recuperata, ma potrebbe non essere così.
In questo momento, per determinare se è accaduto un overflow di memoria, è necessario utilizzare uno strumento! Ci sono due modi
1. Cerca utilizzando lo strumento MAT
Prima di tutto, apri lo strumento Android Device Monitor di AS, come mostrato nell'immagine sottostante:
Dopo aver aperto, apparirà l'interfaccia seguente
Prima di tutto, seleziona il nome del pacchetto dell'applicazione che vuoi testare e fai clic sul punto contrassegnato nell'immagine, apparirà un'icona dopo il nome del pacchetto del programma
Quello che dobbiamo fare ora è far saltare avanti e indietro il nostro app 5 volte
Dopo di che, fare clic sull'icona dell'immagine sottostante per esportare il file hprof per l'analisi
Il file esportato è come mostrato nell'immagine sottostante:
Ora che abbiamo il file hprof, possiamo analizzarlo utilizzando lo strumento MAT
Apri lo strumento MAT
Se non ce l'hai, puoi scaricarlo dall'indirizzo seguente
Indirizzo di download dello strumento MAT
L'interfaccia è come mostrato nell'immagine sottostante:
Apriamo il file hprof esportato in precedenza e, a meno di sorpresa, verrà segnalato l'errore seguente
Questo è perché MAT viene utilizzato per analizzare i file hprof dei programmi java e ha una differenza di formato rispetto agli hprof esportati da Android, quindi dobbiamo convertire i file hprof esportati, lo strumento di conversione fornito dal sdk hprof-conv.exe è nella posizione mostrata nell'immagine
Prossimamente, cd alla directory di questa posizione e esegui il comando per convertire il file hprof, come mostrato nell'immagine sottostante
Il comando hprof-conv viene utilizzato così
hprof-conv file di origine file di output
Ad esempio, hprof-conv E:\aaa.hprof E:\output.hprof
Questo è convertire aaa.hprof in output.hprof e il file output.hprof è il file convertito, mentre mat2.hprof è il file convertito nell'immagine.
Prossimamente, apriamo il file mat2.hprof convertito con lo strumento MAT, come mostrato nell'immagine seguente:
Dopo di che possiamo vedere gli oggetti esistenti nella memoria corrente. Poiché le perdite di memoria si verificano generalmente nell'Activity, dobbiamo solo cercare l'Activity.
Fare clic sull'icona QQL contrassegnata nell'immagine sottostante e inserire select * from instanceof android.app.Activity
Simile a una query SQL per trovare informazioni sull'Activity, fare clic sul triangolo rosso di esecuzione e come mostrato nell'immagine seguente:
Next, we can see the filtered Activity information below
As shown in the figure above, there are still 6 instances of SecondActivity in memory, but we want to exit all of them, which indicates that there is a memory leak
Among them, there are two properties: Shallow size and Retained Size
Shallow Size The size of the memory occupied by the object itself, excluding the objects it references. For non-array types of objects, its size is the sum of the object and all its member variables. Of course, it will also include some data storage units of Java language features. For array types of objects, its size is the sum of the sizes of array element objects. Retained Size Retained Size=Current object size+Total size of objects that can be directly or indirectly referenced by the current object. (The meaning of indirect reference: A->B->C, C is indirect reference) However, when releasing, we also need to exclude objects that are directly or indirectly referenced by GC Roots. They will not be considered as garbage temporarily.
Next, right-click on a SecondActivity
Select with all references
Open the page as shown in the figure below
View the page in the figure below
Seeing this0 referencing thisehticitnethis0 represents the meaning of inner class, that is, an inner class references Activity, and this$0 is referenced by target. Target is a thread, the cause has been found. The reason for memory leakage is that Activity is referenced by an inner class, and the inner class is used by a thread, so it cannot be released. We turn to the code of this class to view
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } } In effetti, esiste
In effetti, esiste un'oggetto di classe interna Runnable in SecondActivity, poi viene utilizzato dal thread, e il thread deve eseguire per 8000 secondi, quindi l'oggetto SecondActivity viene riferito e non può essere rilasciato, causando un memory overflow.
Per risolvere questo tipo di memory overflow, è necessario terminare il thread in tempo quando l'Activity esce (però non è molto buono farlo..), o controllare bene il tempo di esecuzione del thread.
Così abbiamo trovato il memory overflow in questo programma.
2. Utilizzare direttamente il Monitor Memory di Android Studio per trovare il memory overflow
Ancora una volta utilizzando il programma menzionato sopra, lo spiego in modo semplice.
Prima di tutto, eseguire il programma sul telefono, aprire l'interfaccia Monitor di AS per visualizzare l'immagine di Memory
Fare clic sull'icona del camioncino (l'icona nella posizione 1 dell'immagine) per innescare una volta il GC
Fare clic sull'icona nella posizione 2 dell'immagine per visualizzare il file hprof
A sinistra ci sono gli oggetti nella memoria, cercare l'Activity all'interno di essa per vedere se esiste l'Activity che desideriamo essere回收. Se appare l'Activity che ci aspettiamo di essere回收, fare clic su di essa per visualizzare il numero totale su destra, fare clic su uno degli elementi sulla destra può visualizzare la sua struttura delle GC Roots, guardando la struttura si può trovare la posizione del memory leak (simile al primo metodo)
Così è completata la ricerca del memory leak.
Le cause dei memory leak in Android si dividono approssimativamente in seguito tipi:
1. Memory Leak causato dalle variabili statiche
Poiché la vita utile delle variabili statiche inizia con il caricamento della classe e termina con l'unloading, ovvero le variabili statiche vengono rilasciate solo quando il processo del programma muore. Se nell'intervallo delle variabili statiche si riferisce all'Activity, a causa della riferenza, quest'Activity seguirà la vita utile delle variabili statiche e non potrà essere rilasciata, causando un memory leak.
Soluzione:
Quando l'Activity viene riferita da una variabile statica, utilizzare getApplicationContext perché la vita utile dell'Application va dal lancio del programma alla sua fine, come quella delle variabili statiche.
2. Memory Leak causato dal thread
Similmente al caso menzionato negli esempi precedenti, la durata dell'esecuzione del thread è molto lunga, anche se l'Activity esce, continuerà ad eseguire, perché il thread o il Runnable è una classe interna all'Activity, quindi possiede un'istanza dell'Activity (poiché la creazione di una classe interna dipende dall'esterna), quindi causando che l'Activity non possa essere rilasciata.
AsyncTask ha una coda di thread, il problema è più grave
Soluzione:
1. Pianifica in modo ragionevole il tempo di esecuzione dei thread, controlla che i thread si concludano prima dell'Activity.
2. Converti la classe interna in una classe interna statica e usa WeakReference per salvare l'istanza di Activity. Poiché la WeakReference, quando il GC lo rileva, lo recupera, può essere recuperato il prima possibile
3. Utilizzo eccessivo di BitMap memoria
L'analisi di bitmap richiede spazio in memoria, ma la memoria fornisce solo 8M di spazio a BitMap. Se ci sono troppe immagini e non si recicla il bitmap in tempo, può causare un overflow di memoria.
Soluzione:
Recycle le immagini compresse in tempo prima di caricare le immagini
4. Le perdite di memoria causate dalla non chiusura tempestiva delle risorse
Ad esempio, alcuni Cursor non chiudono in tempo, conservando un riferimento all'Activity, causando perdite di memoria
Soluzione:
Chiudi in tempo in onDestory metodo
5. Le perdite di memoria causate dall'uso di Handler
Poiché l'uso di Handler, l'handler invierà l'oggetto message al MessageQueue, quindi Looper eseguirà una scansione del MessageQueue e prenderà Message per eseguire. Tuttavia, se un Message non viene prelevato e eseguito per un lungo periodo, a causa della presenza di un riferimento Handler nel Message e dell'essere Handler in genere un oggetto di classe interna, il Message fa riferimento a Handler, Handler fa riferimento a Activity, il che rende impossibile la回收 di Activity.
Soluzione:
È possibile risolvere il problema utilizzando il modo statico interno+WeakReference
Anche se alcuni oggetti di集合 non sono stati rimossi, gli oggetti registrati non sono stati deregistrati, i problemi di pressione del codice possono anche causare perdite di memoria, ma in generale, le soluzioni menzionate di seguito sono in grado di risolvere il problema.