Panoramica della gestione della memoria

Android Runtime (ART) e la macchina virtuale Dalvik utilizzano il paging e la mappatura della memoria (mmapping) per gestire la memoria. Ciò significa che la memoria modificata da un'app, tramite l'allocazione di nuovi oggetti o il tocco di pagine mappate, rimane residente nella RAM e non può essere eliminata. L'unico modo per rilasciare memoria da un'app è rilasciare i riferimenti agli oggetti archiviati nell'app, rendendo la memoria disponibile per il garbage collector. Ma con un'eccezione: tutti i file inseriti senza modifiche, come il codice, possono essere rimossi dalla RAM se il sistema vuole utilizzare quella memoria altrove.

Questa pagina spiega in che modo Android gestisce i processi delle app e l'allocazione della memoria. Per ulteriori informazioni su come gestire la memoria in modo più efficiente nella tua app, consulta Gestire la memoria dell'app.

Raccolta rifiuti

Un ambiente di memoria gestito, come la macchina virtuale ART o Dalvik, tiene traccia di ogni allocazione della memoria. Una volta determinato che un frammento di memoria non è più utilizzato dal programma, lo libera nuovamente nell'heap, senza alcun intervento da parte del programmatore. Il meccanismo per recuperare la memoria inutilizzata all'interno di un ambiente di memoria gestito è noto come garbage collection. La garbage collection ha due obiettivi: trovare oggetti di dati in un programma a cui non è possibile accedere in futuro e recuperare le risorse utilizzate da questi oggetti.

L'heap di memoria di Android è generazionale, il che significa che esistono diversi bucket di allocazioni che traccia, in base alla durata e alle dimensioni previste di un oggetto allocato. Ad esempio, gli oggetti allocati di recente appartengono alla generazione Young Generation. Quando un oggetto rimane attivo abbastanza a lungo, può essere promosso a una generazione precedente, seguita da una generazione permanente.

Ogni generazione heap ha il proprio limite superiore dedicato alla quantità di memoria che gli oggetti possono occupare. Ogni volta che una generazione inizia a riempirsi, il sistema esegue un evento di garbage collection nel tentativo di liberare memoria. La durata della garbage collection dipende dalla generazione di oggetti che raccoglie e dal numero di oggetti attivi in ogni generazione.

Anche se la garbage collection può essere piuttosto veloce, può comunque influire sulle prestazioni della tua app. In genere non hai il controllo su quando si verifica un evento di garbage collection all'interno del codice. Il sistema dispone di una serie di criteri per determinare quando eseguire la garbage collection. Quando i criteri sono soddisfatti, il sistema interrompe l'esecuzione del processo e avvia la garbage collection. Se la garbage collection si verifica nel corso di un loop di elaborazione intensivo come un'animazione o durante la riproduzione di musica, può aumentare il tempo di elaborazione. Questo aumento può potenzialmente spingere l'esecuzione del codice nella tua app oltre la soglia consigliata di 16 ms per un rendering dei frame efficiente e fluido.

Inoltre, il flusso di codice può eseguire tipi di operazioni che obbligano gli eventi di garbage collection a verificarsi più spesso o a farli durare più a lungo del normale. Ad esempio, se assegni più oggetti nella parte più interna di un loop for durante ogni frame di un'animazione di combinazione alfa, potresti inquinare l'heap di memoria con molti oggetti. In questo caso, il garbage collection esegue più eventi di garbage collection e può ridurre le prestazioni della tua app.

Per informazioni più generali sulla garbage collection, consulta la sezione Garbage collection.

Condividi ricordo

Per soddisfare tutto il suo bisogno di RAM, Android prova a condividere le pagine RAM tra i processi. Può farlo nei seguenti modi:

  • Ogni processo dell'app viene creato con un fork da un processo esistente chiamato Zygote. Il processo Zygote inizia quando il sistema si avvia e carica il codice e le risorse del framework comune (ad esempio i temi di attività). Per avviare un nuovo processo dell'app, il sistema crea un fork del processo Zygote, quindi carica ed esegue il codice dell'app nel nuovo processo. Questo approccio consente di condividere la maggior parte delle pagine RAM allocate per il codice del framework e le risorse tra tutti i processi dell'app.
  • La maggior parte dei dati statici viene inclusa in un processo. Questa tecnica consente di condividere i dati tra i processi e di impaginarli quando necessario. Esempi di dati statici includono: codice Dalvik (inserendolo in un file .odex precollegato per la mmapping diretta), risorse dell'app (progettando la tabella delle risorse in modo che sia una struttura fruibile e allineando le voci ZIP dell'APK) e elementi tradizionali di progetto come il codice nativo nei file .so.
  • In molti luoghi Android condivide la stessa RAM dinamica tra i processi utilizzando regioni di memoria condivisa esplicitamente allocate (con ashmem o gralloc). Ad esempio, le piattaforme delle finestre utilizzano la memoria condivisa tra l'app e il compositore dello schermo, mentre i buffer del cursore utilizzano la memoria condivisa tra il fornitore di contenuti e il client.

A causa dell'ampio utilizzo della memoria condivisa, per determinare la quantità di memoria utilizzata dalla tua app è necessario fare attenzione. Le tecniche per determinare correttamente l'utilizzo della memoria da parte della tua app sono illustrate nella sezione Esaminare l'utilizzo della RAM.

Alloca e recupera memoria dell'app

L'heap Dalvik è limitato a un singolo intervallo di memoria virtuale per ogni processo dell'app. Questo definisce la dimensione logica dell'heap, che può aumentare di conseguenza, ma solo fino a un limite definito dal sistema per ogni app.

La dimensione logica dell'heap non equivale alla quantità di memoria fisica utilizzata dall'heap. Durante l'ispezione dell'heap dell'app, Android calcola un valore chiamato Dimensione set proporzionale (PSS), che tiene conto sia delle pagine sporche che pulite condivise con altri processi, ma solo in una quantità proporzionale al numero di app che condividono la RAM. Questo totale (PSS) è ciò che il sistema considera la tua quantità di memoria fisica. Per ulteriori informazioni su PSS, consulta la guida Indagine sull'utilizzo della RAM.

L'heap Dalvik non compatta le dimensioni logiche dell'heap, il che significa che Android non deframmenta l'heap per avvicinarlo allo spazio. Android può ridurre la dimensione dello heap logica solo se c'è spazio inutilizzato alla fine dell'heap. Tuttavia, il sistema può comunque ridurre la memoria fisica utilizzata dall'heap. Dopo la garbage collection, Dalvik percorre l'heap e trova le pagine inutilizzate, per poi restituirle al kernel usando madvise. Pertanto, le allocazioni in modalità accoppiata e le deallocation di blocchi di grandi dimensioni dovrebbero comportare il recupero di tutta (o quasi tutta) la memoria fisica utilizzata. Tuttavia, recuperare memoria da piccole allocazioni può essere molto meno efficiente perché la pagina utilizzata per un'allocazione ridotta potrebbe comunque essere condivisa con qualcos'altro non ancora liberato.

Limita memoria app

Per mantenere un ambiente multitasking funzionale, Android imposta un limite fisso alla dimensione dello heap per ogni app. Il limite esatto per le dimensioni dell'heap varia da un dispositivo all'altro in base alla quantità di RAM disponibile complessivamente sul dispositivo. Se l'app ha raggiunto la capacità di heap e tenta di allocare più memoria, può ricevere un OutOfMemoryError.

In alcuni casi, potresti eseguire query al sistema per determinare esattamente la quantità di spazio heap disponibile sul dispositivo attuale, ad esempio per stabilire la quantità di dati da conservare al sicuro in una cache. Puoi eseguire una query al sistema per questa figura chiamando getMemoryClass(). Questo metodo restituisce un numero intero che indica il numero di megabyte disponibili per l'heap dell'app.

Cambio di app

Quando gli utenti passano da un'app all'altra, Android conserva in una cache le app che non sono in primo piano, ovvero non visibili all'utente o che eseguono un servizio in primo piano come la riproduzione di musica. Ad esempio, la prima volta che un utente avvia un'app, viene creato un processo apposito, ma quando l'utente esce dall'app, il processo non viene chiuso. Il sistema mantiene il processo nella cache. Se in un secondo momento l'utente torna all'app, il sistema riutilizza il processo, velocizzando così il passaggio all'app.

Se la tua app ha un processo memorizzato nella cache e conserva le risorse di cui non ha attualmente bisogno, l'app, anche quando l'utente non la utilizza, influisce sulle prestazioni generali del sistema. Poiché il sistema esaurisce le risorse come la memoria, termina i processi nella cache. Il sistema prende in considerazione anche i processi che occupano la maggior parte della memoria e può arrestarli per liberare RAM.

Nota: meno memoria consuma l'app mentre si trova nella cache, maggiori sono le possibilità che non venga interrotta e possa essere ripristinata rapidamente. Tuttavia, a seconda dei requisiti di sistema istantanei, è possibile terminare i processi memorizzati nella cache in qualsiasi momento, indipendentemente dall'utilizzo delle risorse.

Per ulteriori informazioni su come i processi vengono memorizzati nella cache quando non sono in esecuzione in primo piano e su come Android decide quali possono essere terminati, consulta la guida Processi e thread.

Stress test per la memoria

Anche se i problemi di memoria sono meno comuni sui dispositivi di fascia alta, possono comunque causare problemi agli utenti che usano dispositivi con RAM ridotta, ad esempio quelli che eseguono Android Go. È importante provare a riprodurre questo ambiente con memoria insufficiente in modo da poter scrivere test di strumentazione per verificare il comportamento dell'app e migliorare l'esperienza degli utenti con dispositivi con memoria ridotta.

Test applicazione stressante

Stressful Application Test (stressapptest) è un test dell'interfaccia di memoria che consente di creare situazioni realistiche e ad alto carico per testare varie limitazioni di memoria e hardware per la tua app. Grazie alla capacità di definire limitazioni di tempo e memoria, consente di scrivere la strumentazione per verificare gli incontri reali di situazioni con memoria elevata. Ad esempio, utilizza il seguente insieme di comandi per eseguire il push della libreria statica nel file system di dati, renderla eseguibile ed eseguire uno stress test per 20 secondi di 990 MB:
    adb push stressapptest /data/local/tmp/
    adb shell chmod 777 /data/local/tmp/stressapptest
    adb shell /data/local/tmp/stressapptest -s 20 -M 990

  

Consulta la documentazione stressapptest per ulteriori informazioni sull'installazione dello strumento, sugli argomenti comuni e sulla gestione degli errori.

Osservazioni su stressapptest

Strumenti come stressapptest possono essere utilizzati per richiedere allocazioni di memoria superiori a quelle liberamente disponibili. Questo tipo di richiesta può generare vari avvisi, di cui dovresti essere a conoscenza dal lato sviluppo. I tre avvisi principali che possono essere generati a causa della scarsa disponibilità di memoria includono:
  • SIGABRT: si tratta di un arresto anomalo nativo irreversibile per il tuo processo a causa della richiesta di allocazioni di dimensioni superiori alla memoria libera, mentre la memoria del sistema è già eccessiva.
  • SIGQUIT: produce un dump della memoria del core e termina il processo quando viene rilevato dal test di strumentazione.
  • TRIM_MEMORY_EVENTS: questi callback sono disponibili su Android 4.1 (livello API 16) e versioni successive e forniscono avvisi dettagliati sulla memoria per il processo.