Perché non liberare memoria non appena il suo contatore di riferimento raggiunge lo zero [duplicato]

9

Molti linguaggi come Java e C # hanno i garbage collector che liberano memoria quando quella memoria non ha più alcun riferimento. Tuttavia non lo liberano immediatamente dopo che il contatore di riferimento ha raggiunto lo zero, ma invece ogni tanto controllano tutta la memoria per vedere se quella memoria ha qualche riferimento ed eliminarla se non lo fa. Qual è il vantaggio di fare in questo modo?

Il rovescio della medaglia è che si perde il distruttore in quanto non si può garantire quando verrà chiamato.

Immagino che sia fatto in quel modo a causa delle prestazioni, ma c'è stato qualche studio che mostra che un garbage collector che funziona in questo modo ha una performance migliore di quella di std::shared_ptr trovata in C ++?

    
posta Caesar 10.12.2013 - 00:33
fonte

4 risposte

23

Perché per liberare memoria non appena il contatore di riferimento raggiunge lo zero, devi mantenere un contatore di riferimento. E questo non viene gratis. In genere, limita la velocità effettiva.

Di solito ci sono due strategie principali per implementare i garbage collector: traccianti e collezionisti di conteggio di riferimento. (Ce ne sono altri, ma quelli sono quelli in uso nella maggior parte dei sistemi di gestione della memoria automatica tradizionali.)

In genere, i GC di conteggio di riferimento tendono ad avere un throughput peggiore ma una latenza migliore (e più prevedibile) rispetto alla traccia dei collettori, mentre i collettori di tracciamento hanno un rendimento migliore ma una latenza più elevata e meno prevedibile. Un altro grosso problema con (almeno con semplici implementazioni) il conteggio dei riferimenti ai garbage collector è che non possono eseguire cicli di raccolta dati. In genere, è necessario eseguire un raccoglitore di traccia insieme ad un raccoglitore conteggio di riferimento (ad esempio, ad esempio, CPython).

In pratica, tutti i moderni sistemi di gestione della memoria automatica ad alte prestazioni di livello industriale (tutti i collector di Oracle JDK, Oracle JRockit e molti altri JVM, Microsoft CLR, Mono, la maggior parte delle implementazioni ECMAScript, tutte le implementazioni di Ruby, quasi tutti i Python implementazioni, tutte le implementazioni Smalltalk, tutte le implementazioni Lisp, ecc.) stanno raccogliendo i collezionisti, quindi c'è un po 'di loop di feedback auto-rinforzante qui: più soldi vengono messi in ricerca sul tracciamento dei GC perché sono popolari e diventano più popolari perché migliorano grazie ai soldi spesi per le loro ricerche ... e così via.

    
risposta data 10.12.2013 - 01:41
fonte
14

Perché sarebbe troppo costoso farlo continuamente.

"Segna e spazza" i garbage collector periodicamente spazzano lo spazio di memoria per gli oggetti che sono stati sottoposti a dereferenziazione. I garbage collector generazionali eliminano prima quegli oggetti che sono stati probabilmente eliminati di recente (che risulta essere statisticamente gli oggetti creati più di recente). Oggetti che sono stati in giro per un po 'vengono messi in una seconda o terza generazione per spazzare più tardi.

La raccolta dei dati inutili funziona molto come il cestino: il tuo uomo della spazzatura non controlla la tua lattina ogni minuto per la spazzatura, o anche tutti i giorni, anche se potresti controllare le lattine all'interno della tua casa ogni giorno. Controllare ogni minuto o ogni ora sarebbe troppo inefficiente, sia per te che per l'uomo della spazzatura.

    
risposta data 10.12.2013 - 00:42
fonte
2

La raccolta dei rifiuti basata sul conteggio dei riferimenti è molto più lenta della traccia dei garbage collector ( Spinta shared_ptr vs altre soluzioni , oppure puoi trovare collegamenti a più confronti scientifici presso sito web di Boehm GC ). Inoltre, il conteggio dei riferimenti da solo non è soddisfacente, perché non può gestire cicli di riferimento .

I programmatori C ++ tendono a pensare alla memoria come a una risorsa, dato che C ++ gestisce la memoria e altre risorse allo stesso modo. La raccolta di dati inutili è un approccio speciale che può gestire solo la memoria, che di fatto è la risorsa più utilizzata.

Questo spesso confonde i programmatori C ++ - tendono a pensare ai finalizzatori come un povero sostituto per i distruttori C ++ , che non lo sono. Per rilasciare sparse risorse (non per memoria) in modo deterministico, usa finalmente blocchi.

    
risposta data 10.12.2013 - 12:43
fonte
1

Un riferimento in una JVM a 64 bit utilizza in genere 4 byte di memoria (usando oops compresso) Ciò significa che l'utilizzo di un conteggio di riferimento aumenterà la dimensione della memoria per oggetto, ma aumenterà anche la complessità dei riferimenti di passaggio. Ogni volta che un thread deve ottenere un riferimento a un oggetto, è necessario incrementarlo e successivamente decrementare il contatore, cosa che un sistema di base GC non ha bisogno di fare.

BTW in C ++ un puntatore condiviso ha due puntatori (ciascuno a 64 bit) uno per l'oggetto e uno per il contatore di riferimento. Su molti sistemi questo contatore di riferimento usa 32 byte poiché questo è il costo minimo di allocazione. Nei puntatori condivisi totali, sostituire un riferimento a 4 byte con un puntatore condiviso a 16 byte e un riferimento a 32 byte. Non sembra molto adatto alla cache della CPU.

Il problema con i GC JVM sono le collezioni stop-the-world che vanno bene per l'elaborazione del throughput ma un vero problema per i sistemi a bassa latenza. Un modo per aggirare questo è usare un raccoglitore concorrente, il migliore è lo Zing di Azul che è una pausa in meno (anche se non è privo di pause;)

    
risposta data 10.12.2013 - 11:32
fonte

Leggi altre domande sui tag