Panoramica della gestione della memoria

Il runtime Android (ART) e la macchina virtuale Dalvik utilizzano la paginazione e la mappatura della memoria (mmapping) per gestire la memoria. Ciò significa che qualsiasi memoria modificata da un'app, che si tratti di allocazione di nuovi oggetti o di accesso a pagine mappate, rimane residente nella RAM e non può essere espulsa dalla pagina. L'unico modo per liberare memoria da un'app è rilasciare i riferimenti agli oggetti detenuti dall'app, rendendo la memoria disponibile per il garbage collector. Tranne che per un'eccezione: tutti i file caricati in memoria virtuale senza modifiche, ad esempio il codice, possono essere espulsi dalla RAM se il sistema vuole utilizzare la memoria altrove.

Questa pagina spiega come 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.

Garbage collection

Un ambiente di memoria gestito, come la macchina virtuale ART o Dalvik, monitora ogni allocazione di memoria. Una volta stabilito che un blocco di memoria non viene più utilizzato dal programma, lo restituisce all'heap, senza alcun intervento del programmatore. Il meccanismo per recuperare la memoria inutilizzata in un ambiente di memoria gestita è noto come garbage collection. La raccolta dei rifiuti ha due obiettivi: trovare oggetti di dati in un programma a cui non sarà possibile accedere in futuro; e recuperare le risorse utilizzate da questi oggetti.

L'heap di memoria di Android è di tipo generazionale, il che significa che vengono monitorati diversi bucket di allocazioni in base alla durata e alle dimensioni previste di un oggetto allocato. Ad esempio, gli oggetti allocati di recente appartengono alla generazione giovane. Quando un oggetto rimane attivo per un periodo di tempo sufficientemente lungo, può essere promosso a una generazione precedente, seguita da una generazione permanente.

Ogni generazione dell'heap ha il proprio limite massimo dedicato per la quantità di memoria che gli oggetti possono occupare. Ogni volta che una generazione inizia a riempirsi, il sistema esegue un evento di raccolta del garbage nel tentativo di liberare memoria. La durata del garbage collection dipende dalla generazione di oggetti che viene raccolta e dal numero di oggetti attivi in ogni generazione.

Anche se la raccolta dei rifiuti può essere abbastanza rapida, può comunque influire sulle prestazioni della tua app. In genere non controlli quando si verifica un evento di raccolta dei rifiuti all'interno del codice. Il sistema dispone di un insieme di criteri per determinare quando eseguire la raccolta del garbage. Quando i criteri sono soddisfatti, il sistema interrompe l'esecuzione del processo e avvia la raccolta dei rifiuti. Se la raccolta dei rifiuti avviene nel mezzo di un ciclo di elaborazione intensivo, come un'animazione o durante la riproduzione di musica, il tempo di elaborazione può aumentare. 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 potrebbe eseguire tipi di attività che fanno sì che gli eventi di raccolta dei rifiuti si verifichino più spesso o durino più a lungo del normale. Ad esempio, se allochi più oggetti nella parte più interna di un ciclo for durante ogni fotogramma di un'animazione di miscelazione alpha, potresti inquinare l'heap della memoria con molti oggetti. In questo caso, il garbage collector esegue più eventi di raccolta del garbage e può peggiorare le prestazioni dell'app.

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

Condividere un ricordo

Per far entrare tutto ciò di cui ha bisogno nella RAM, Android cerca di condividere le pagine della RAM tra i processi. Lo può fare nei seguenti modi:

  • Ogni processo dell'app viene creato da un processo esistente chiamato Zygote. Il processo Zygote si avvia all'avvio del sistema e carica il codice e le risorse comuni del framework (ad esempio i temi delle attività). Per avviare un nuovo processo dell'app, il sistema esegue il 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 di RAM allocate per il codice e le risorse del framework tra tutti i processi dell'app.
  • La maggior parte dei dati statici viene mappata in un processo. Questa tecnica consente di condividere i dati tra i processi e di paginarli se necessario. Ecco alcuni esempi di dati statici: codice Dalvik (se inserito in un file .odex prelinkato per l'allocazione diretta in memoria virtuale), risorse dell'app (se la tabella delle risorse è progettata come struttura che può essere allocata in memoria virtuale e se le voci zip dell'APK sono allineate) e elementi di progetto tradizionali (come il codice nativo nei file .so).
  • In molti casi, Android condivide la stessa RAM dinamica tra i processi utilizzando regioni di memoria condivisa allocate in modo esplicito (con ashmem o gralloc). Ad esempio, le superfici 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'uso intensivo della memoria condivisa, è necessario prestare attenzione al calcolo della quantità di memoria utilizzata dall'app. Le tecniche per determinare correttamente l'utilizzo della memoria della tua app sono descritte in Esaminare l'utilizzo della RAM.

Allocazione e recupero della memoria dell'app

L'heap di Dalvik è limitato a un singolo intervallo di memoria virtuale per ogni processo dell'app. Questo definisce le dimensioni dell'heap logico, che possono crescere in base alle esigenze, ma solo fino a un limite definito dal sistema per ogni app.

Le dimensioni logiche dell'heap non corrispondono alla quantità di memoria fisica utilizzata dall'heap. Quando ispeziona l'heap dell'app, Android calcola un valore chiamato dimensione del set proporzionale (PSS), che tiene conto sia delle pagine sporche che di quelle pulite condivise con altri processi, ma solo in un quantitativo proporzionale al numero di app che condividono quella RAM. Questo valore totale (PSS) corrisponde a quanto il sistema considera come impronta di memoria fisica. Per ulteriori informazioni sul PSS, consulta la guida Esaminare l'utilizzo della RAM.

L'heap di Dalvik non comprime le dimensioni logiche dell'heap, il che significa che Android non defragmenta l'heap per recuperare spazio. Android può ridurre le dimensioni dell'heap logico solo se è presente spazio inutilizzato alla fine dell'heap. Tuttavia, il sistema può comunque ridurre la memoria fisica utilizzata dall'heap. Dopo la raccolta dei rifiuti, Dalvik esamina l'heap e trova le pagine inutilizzate, poi le restituisce al kernel utilizzando madvise. Pertanto, le allocazioni e le deallocazioni associate di blocchi di grandi dimensioni dovrebbero comportare il recupero di tutta (o quasi tutta) la memoria fisica utilizzata. Tuttavia, recuperare la memoria da allocazioni piccole può essere molto meno efficiente perché la pagina utilizzata per una piccola allocazione potrebbe essere ancora condivisa con qualcos'altro che non è stato ancora liberato.

Limitare la memoria dell'app

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

In alcuni casi, potresti voler eseguire una query sul sistema per determinare esattamente quanto spazio heap hai a disposizione sul dispositivo corrente, ad esempio per determinare la quantità di dati che è possibile conservare in una cache. Puoi eseguire una query sul sistema per ottenere questo valore 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 sono visibili all'utente o non eseguono un servizio in primo piano come la riproduzione di musica. Ad esempio, quando un utente avvia un'app per la prima volta, viene creato un processo, ma quando l'utente esce dall'app, il processo non viene chiuso. Il sistema mantiene la procedura nella cache. Se l'utente torna in un secondo momento all'app, il sistema riutilizza la procedura, rendendo così più veloce il passaggio da un'app all'altra.

Se la tua app ha un processo memorizzato nella cache e conserva risorse di cui al momento non ha bisogno, l'app, anche se non è in uso, influisce sul rendimento complessivo del sistema. Quando le risorse del sistema, come la memoria, si esauriscono, vengono terminati i processi nella cache. Il sistema tiene conto anche dei processi che occupano la maggior parte della memoria e può interromperli per liberare la RAM.

Nota:minore è la memoria consumata dall'app in cache, maggiori sono le probabilità che non venga interrotta e che possa essere ripresa rapidamente. Tuttavia, a seconda dei requisiti di sistema istantanei, è possibile che i processi memorizzati nella cache vengano interrotti in qualsiasi momento, indipendentemente dall'utilizzo delle risorse.

Per ulteriori informazioni su come le attività vengono memorizzate nella cache quando non sono in esecuzione in primo piano e su come Android decide quali possono essere interrotte, consulta la guida Processi e thread.

Stress test della memoria

Sebbene i problemi di stress della memoria siano meno comuni sui dispositivi di fascia alta, possono comunque causare problemi per gli utenti che utilizzano dispositivi con poca RAM, ad esempio quelli con Android (versione Go). È importante provare a riprodurre questo ambiente con elevato utilizzo della memoria in modo da poter scrivere test di instrumentation per verificare il comportamento dell'app e migliorare l'esperienza degli utenti su dispositivi con poca memoria.

Test di stress dell'applicazione

Lo stress test delle applicazioni (stressapptest) è un test dell'interfaccia di memoria che consente di creare situazioni realistiche ad alto carico per testare varie limitazioni di memoria e hardware per la tua app. Con la possibilità di definire limitazioni di tempo e memoria, consente di scrivere strumenti per verificare le situazioni reali con un utilizzo elevato della memoria. Ad esempio, utilizza il seguente insieme di comandi per spingere la libreria statica nel file system dei dati, renderla eseguibile ed eseguire un test di stress 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 di 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 disponibili senza costi. Questo tipo di richiesta può generare vari avvisi, che devi conoscere dal punto di vista dello sviluppo. I tre avvisi principali che possono essere generati a causa della scarsa disponibilità di memoria sono:
  • SIGABRT: si tratta di un arresto anomalo nativo fatale per il processo a causa della richiesta di allocazioni di dimensioni superiori alla memoria libera, mentre il sistema è già sotto pressione di memoria.
  • SIGQUIT: genera un dump della memoria principale 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.