Perché la garbage collection si estende solo alla memoria e non ad altri tipi di risorse?


12

Sembra che le persone si siano stancate della gestione manuale della memoria, quindi hanno inventato la raccolta dei rifiuti e la vita era abbastanza buona. Ma che dire di ogni altro tipo di risorsa? Descrittori di file, socket o persino dati creati dall'utente come connessioni al database?

Sembra una domanda ingenua ma non riesco a trovare un posto dove qualcuno l'abbia chiesto. Consideriamo i descrittori di file. Supponiamo che un programma sappia che sarà consentito avere solo 4000 fds disponibili all'avvio. Ogni volta che esegue un'operazione che aprirà un descrittore di file, cosa succede se lo facesse

  1. Verificare che non si stia esaurendo.
  2. In tal caso, attivare il Garbage Collector, che libererà un sacco di memoria.
  3. Se parte della memoria liberata conteneva riferimenti ai descrittori di file, chiuderli immediatamente. Sa che la memoria apparteneva a una risorsa perché la memoria legata a quella risorsa era registrata in un "registro descrittore di file", per mancanza di un termine migliore, quando è stata aperta per la prima volta.
  4. Aprire un nuovo descrittore di file, copiarlo nella nuova memoria, registrare quella posizione di memoria nel "registro dei descrittori di file" e restituirlo all'utente.

Quindi la risorsa non verrebbe liberata prontamente, ma verrebbe liberata ogni volta che funzionava il gc che include almeno, proprio prima che la risorsa stesse per esaurirsi, supponendo che non venisse interamente utilizzata.

E sembra che sarebbe sufficiente per molti problemi di pulizia delle risorse definiti dall'utente. Sono riuscito a trovare un singolo commento qui che fa riferimento a una pulizia simile a questa in C ++ con un thread che contiene un riferimento a una risorsa e lo pulisce quando rimane solo un riferimento (dal thread di pulizia), ma posso ' t trova alcuna prova del fatto che questa è una biblioteca o parte di qualsiasi lingua esistente.

Risposte:


4

GC si occupa di una risorsa prevedibile e riservata . La VM ha il controllo totale su di essa e ha il controllo totale su quali istanze vengono create e quando. Le parole chiave qui sono "riservato" e "controllo totale". I gestori sono allocati dal sistema operativo e i puntatori sono ... bene puntatori a risorse allocate al di fuori dello spazio gestito. Per questo motivo, handle e puntatori non sono limitati per essere utilizzati all'interno del codice gestito. Possono essere utilizzati - e spesso lo sono - da codice gestito e non gestito in esecuzione sullo stesso processo.

Un "raccoglitore di risorse" sarebbe in grado di verificare se un handle / pointer viene utilizzato o meno all'interno di uno spazio gestito, ma per definizione non è a conoscenza di ciò che sta accadendo al di fuori del suo spazio di memoria (e, per peggiorare le cose, è possibile utilizzare alcuni handle oltre i confini del processo).

Un esempio pratico è il CLR .NET. Si può usare il C ++ aromatizzato per scrivere codice che funziona con spazi di memoria sia gestiti che non gestiti; handle, puntatori e riferimenti possono essere passati tra codice gestito e non gestito. Il codice non gestito deve utilizzare costrutti / tipi speciali per consentire al CLR di tenere traccia dei riferimenti fatti alle sue risorse gestite. Ma è il massimo che può fare. Non può fare lo stesso con handle e puntatori, e per questo motivo il suddetto Resource Collector non saprebbe se va bene rilasciare un handle o un puntatore particolare.

modifica: per quanto riguarda il CLR .NET, non ho esperienza con lo sviluppo di C ++ con la piattaforma .NET. Forse esistono meccanismi speciali che consentono al CLR di tenere traccia dei riferimenti a handle / puntatori tra codice gestito e non gestito. In tal caso, il CLR potrebbe occuparsi della vita di quelle risorse e rilasciarle quando vengono cancellati tutti i riferimenti (beh, almeno in alcuni scenari potrebbe). Ad ogni modo, le migliori pratiche impongono che handle (specialmente quelli che puntano a file) e puntatori debbano essere rilasciati non appena non sono necessari. Un Collezionista di risorse non si conformerebbe a ciò, questa è un'altra ragione per non averne uno.

modifica 2: È relativamente banale su CLR / JVM / VM in generale scrivere del codice per liberare un determinato handle se viene utilizzato solo all'interno dello spazio gestito. In .NET sarebbe qualcosa di simile:

// This class offends many best practices, but it would do the job.
public class AutoReleaseFileHandle {
    // keeps track of how many instances of this class is in memory
    private static int _toBeReleased = 0;

    // the threshold when a garbage collection should be forced
    private const int MAX_FILES = 100;

    public AutoReleaseFileHandle(FileStream fileStream) {
       // Force garbage collection if max files are reached.
       if (_toBeReleased >= MAX_FILES) {
          GC.Collect();
       }
       // increment counter
       Interlocked.Increment(ref _toBeReleased);
       FileStream = fileStream;
    }

    public FileStream { get; private set; }

    private void ReleaseFileStream(FileStream fs) {
       // decrement counter
       Interlocked.Decrement(ref _toBeReleased);
       FileStream.Close();
       FileStream.Dispose();
       FileStream = null;
    }

    // Close and Dispose the Stream when this class is collected by the GC.
    ~AutoReleaseFileHandle() {
       ReleaseFileStream(FileStream);
    }

    // because it's .NET this class should also implement IDisposable
    // to allow the user to dispose the resources imperatively if s/he wants 
    // to.
    private bool _disposed = false;
    public void Dispose() {
      if (_disposed) {
        return;
      }
      _disposed = true;
      // tells GC to not call the finalizer for this instance.
      GC.SupressFinalizer(this);

      ReleaseFileStream(FileStream);
    }
}

// use it
// for it to work, fs.Dispose() should not be called directly,
var fs = File.Open("path/to/file"); 
var autoRelease = new AutoReleaseFileHandle(fs);

3

Questo sembra essere uno dei motivi per cui le lingue con i raccoglitori di rifiuti implementano i finalizzatori. I finalizzatori hanno lo scopo di consentire a un programmatore di ripulire le risorse di un oggetto durante la garbage collection. Il grosso problema con i finalizzatori è che non sono garantiti per l'esecuzione.

C'è un bel riassunto sull'uso dei finalizzatori qui:

Completamento e pulizia dell'oggetto

In effetti, utilizza specificamente il descrittore di file come esempio. Dovresti assicurarti di ripulire da solo tale risorsa, ma esiste un meccanismo che può ripristinare risorse che non sono state rilasciate correttamente.


Non sono sicuro che questo risponda alla mia domanda. Manca la parte della mia proposta in cui il sistema sa che sta per esaurire una risorsa. L'unico modo per eseguire il martellamento di quella parte è assicurarsi di eseguire manualmente gc prima di allocare nuovi descrittori di file, ma ciò è estremamente inefficiente e non so se si può anche far funzionare gc in java.
mindreader

OK, ma i descrittori di file di solito rappresentano un file aperto nel sistema operativo che implica (a seconda del sistema operativo) l'uso di risorse a livello di sistema come blocchi, pool di buffer, pool di strutture, ecc. Francamente, non vedo il vantaggio di lasciare aperte queste strutture per una successiva raccolta di rifiuti e vedo molti svantaggi nel lasciarle allocate più a lungo del necessario. I metodi Finalize () hanno lo scopo di consentire un'ultima pulizia del fossato nel caso in cui un programmatore abbia trascurato le chiamate per ripulire le risorse, ma su cui non fare affidamento.
Brian Hibbert,

La mia comprensione è che la ragione per cui non dovrebbero essere invocati è che se dovessi allocare una tonnellata di queste risorse, come forse stai scendendo in una gerarchia di file aprendo ogni file, potresti aprire troppi file prima che il gc accada a correre, causando un esplosione. La stessa cosa accadrebbe con la memoria, tranne per il fatto che il runtime verifica che non si esaurisca la memoria. Vorrei sapere perché un sistema non può essere implementato per recuperare risorse arbitrarie prima dell'esplosione, quasi nello stesso modo della memoria.
mindreader

Un sistema POTREBBE essere scritto su risorse GC diverse dalla memoria, ma è necessario tenere traccia dei conteggi di riferimento o disporre di altri metodi per determinare quando una risorsa non è più in uso. NON si desidera deallocare e riallocare risorse ancora in uso. Tutto il feudo di caos può derivare se un thread ha un file aperto per la scrittura, il sistema operativo "recupera" l'handle del file e un altro thread apre un altro file per la scrittura usando lo stesso handle. E suggerirei anche che è uno spreco di risorse significative lasciarle aperte fino a quando un thread come GC non riesce a rilasciarle.
Brian Hibbert,

3

Esistono molte tecniche di programmazione per aiutare a gestire questo tipo di risorse.

  • I programmatori C ++ usano spesso un modello chiamato Resource Acquisition is Initialization , o RAII in breve. Questo modello assicura che quando un oggetto che si attacca alle risorse non rientra nell'ambito, chiuderà le risorse a cui si stava aggrappando. Ciò è utile quando la durata dell'oggetto corrisponde a un determinato ambito nel programma (ad esempio, quando corrisponde al momento in cui un particolare stack frame è presente nello stack), quindi è utile per gli oggetti a cui fanno riferimento variabili locali (puntatore variabili archiviate nello stack), ma non molto utile per gli oggetti a cui sono puntati i puntatori memorizzati nell'heap.

  • Java, C # e molte altre lingue forniscono un modo per specificare un metodo che verrà invocato quando un oggetto non è più attivo e sta per essere raccolto dal garbage collector. Vedi, ad esempio, finalizzatori dispose()e altri. L'idea è che il programmatore possa implementare un tale metodo in modo che chiuda esplicitamente la risorsa prima che l'oggetto venga liberato dal Garbage Collector. Tuttavia, questi approcci hanno alcuni problemi, che puoi leggere altrove; per esempio, il Garbage Collector potrebbe non raccogliere l'oggetto fino a molto tempo dopo quello che desideri.

  • C # e altre lingue forniscono una usingparola chiave che aiuta a garantire che le risorse vengano chiuse dopo che non sono più necessarie (quindi non dimenticare di chiudere il descrittore di file o altre risorse). Spesso è meglio che fare affidamento sul Garbage Collector per scoprire che l'oggetto non è più attivo. Vedi, ad esempio, /programming//q/75401/781723 . Il termine generale qui è una risorsa gestita . Questa nozione si basa su RAII e finalizzatori, migliorandoli in qualche modo.


Sono meno interessato alla rapida deallocazione delle risorse e più interessato all'idea della deallocazione just in time. RIAA è eccezionale, ma non super applicabile a molte lingue di raccolta dei rifiuti. A Java manca la possibilità di sapere quando sta per esaurire una determinata risorsa. L'uso e le operazioni di tipo parentesi sono utili e gestiscono gli errori, ma non mi interessano. Voglio semplicemente allocare le risorse e poi si puliranno da sole ogni volta che è conveniente o necessario, e c'è poco modo per rovinare tutto. Immagino che nessuno ci abbia davvero pensato.
mindreader

2

Tutta la memoria è uguale, se chiedo 1K, non mi interessa da dove provenga l'1K.

Quando chiedo un handle di file, voglio un handle per il file che desidero aprire. Avere un handle di file aperto su un file, spesso blocca l'accesso al file da altri processi o macchine.

Pertanto, gli handle di file devono essere chiusi non appena non sono necessari, altrimenti bloccano altri accessi al file, ma la memoria deve essere recuperata solo quando si avvia l'esaurimento.

L'esecuzione di un GC pass è costosa e viene eseguita solo "quando necessario", non è possibile prevedere quando un altro processo avrà bisogno di un handle di file che potrebbe non essere più utilizzato dal processo, ma che è ancora aperto.


La tua risposta è la vera chiave: la memoria è fungibile e la maggior parte dei sistemi ne ha abbastanza che non è necessario recuperarla in modo particolarmente rapido. Al contrario, se un programma acquisisce l'accesso esclusivo a un file, questo bloccherà qualsiasi altro programma nell'universo che potrebbe aver bisogno di usare quel file, indipendentemente da quanti altri file possano esistere.
supercat

0

Immagino che il motivo per cui questo non è stato affrontato molto per altre risorse è esattamente perché si preferisce che la maggior parte delle altre risorse vengano rilasciate il prima possibile per essere riutilizzate da chiunque.

Nota, ovviamente, il tuo esempio potrebbe essere fornito ora usando descrittori di file "deboli" con tecniche GC esistenti.


0

Controllare se la memoria non è più accessibile (e quindi garantisce di non essere più utilizzata) è piuttosto semplice. La maggior parte degli altri tipi di risorse può essere gestita più o meno con le stesse tecniche (vale a dire, l'acquisizione delle risorse è l'inizializzazione, RAII e la sua controparte di liberazione quando l'utente viene distrutto, che lo collega all'amministrazione della memoria). Fare una sorta di liberazione "just in time" è impossibile in generale (controlla il problema di arresto, dovresti scoprire che alcune risorse sono state utilizzate per l'ultima volta). Sì, a volte può essere fatto automaticamente, ma è un caso molto più complicato come memoria. Quindi si basa principalmente sull'intervento dell'utente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.