In poche parole
La finalizzazione non è una questione semplice da gestire per i garbage collector. È facile da usare con il conteggio dei riferimenti GC, ma questa famiglia di GC è spesso incompleta e richiede che le perdite di memoria siano compensate dall'innesco esplicito di distruzione e finalizzazione di alcuni oggetti e strutture. La tracciabilità dei raccoglitori di rifiuti è molto più efficace, ma rende molto più difficile identificare l'oggetto da finalizzare e distruggere, al contrario di identificare semplicemente la memoria inutilizzata, richiedendo quindi una gestione più complessa, con un costo in termini di tempo e spazio e complessità l'implemento.
introduzione
Suppongo che ciò che stai chiedendo sia il motivo per cui le lingue della garbage collection non gestiscono automaticamente la distruzione / finalizzazione all'interno del processo di garbage collection, come indicato dall'osservazione:
Trovo estremamente carente che queste lingue considerino la memoria come l'unica risorsa che valga la pena di essere gestita. Che dire di socket, handle di file, stati delle applicazioni?
Non sono d'accordo con la risposta accettata data da kdbanman . Mentre i fatti dichiarati sono per lo più corretti, sebbene fortemente orientati al conteggio dei riferimenti, non credo che spieghino correttamente la situazione lamentata nella domanda.
Non credo che la terminologia sviluppata in quella risposta costituisca un grosso problema, ed è più probabile che confonda le cose. In effetti, come presentato, la terminologia è principalmente determinata dal modo in cui le procedure sono attivate piuttosto che da ciò che fanno. Il punto è che in tutti i casi è necessario finalizzare un oggetto non più necessario con un processo di pulizia e liberare qualsiasi risorsa abbia utilizzato, la memoria è solo una di queste. Idealmente, tutto dovrebbe essere fatto automaticamente quando l'oggetto non deve più essere utilizzato, tramite un garbage collector. In pratica, GC potrebbe mancare o presentare carenze, e ciò è compensato dall'attivazione esplicita del programma di finalizzazione e bonifica.
Il trigerring esplicito da parte del programma è un problema poiché può consentire errori di programmazione difficili da analizzare, quando un oggetto ancora in uso viene esplicitamente terminato.
Quindi è molto meglio fare affidamento sulla raccolta automatica dei rifiuti per recuperare le risorse. Ma ci sono due problemi:
alcune tecniche di garbage collection consentiranno perdite di memoria che impediscono il pieno recupero delle risorse. Questo è ben noto per il conteggio dei riferimenti GC, ma può apparire per altre tecniche GC quando si utilizzano alcune organizzazioni di dati senza cura (punto non discusso qui).
mentre la tecnica GC può essere efficace nell'identificare le risorse di memoria non più utilizzate, finalizzare gli oggetti in esse contenuti potrebbe non essere semplice e ciò complica il problema del recupero di altre risorse utilizzate da questi oggetti, che è spesso lo scopo della finalizzazione.
Infine, un punto importante spesso dimenticato è che i cicli GC possono essere innescati da qualsiasi cosa, non solo dalla carenza di memoria, se vengono forniti gli hook adeguati e se il costo di un ciclo GC ne vale la pena. Quindi è perfettamente OK avviare un GC quando manca qualsiasi tipo di risorsa, nella speranza di liberarne un po '.
Riferimenti che contano i netturbini
Il conteggio dei riferimenti è una tecnica di raccolta dei rifiuti debole , che non gestirà correttamente i cicli. Sarebbe davvero debole nel distruggere le strutture obsolete e nel recuperare altre risorse semplicemente perché è debole nel recuperare la memoria. Ma i finalizzatori possono essere usati più facilmente con un garbage collector (GC) di conteggio dei riferimenti, poiché un GC di conteggio dei ref reclama una struttura quando il suo conteggio dei ref scende fino a 0, a quel punto il suo indirizzo è noto insieme al suo tipo, sia staticamente o dinamicamente. Quindi è possibile recuperare la memoria proprio dopo aver applicato il finalizzatore appropriato e aver chiamato ricorsivamente il processo su tutti gli oggetti appuntiti (possibilmente tramite la procedura di finalizzazione).
In breve, la finalizzazione è facile da implementare con Ref Counting GC, ma soffre della "incompletezza" di quel GC, in effetti a causa di strutture circolari, esattamente nella stessa misura in cui soffre il recupero della memoria. In altre parole, con il conteggio dei riferimenti, la memoria è gestita in modo inadeguato esattamente come altre risorse come socket, handle di file, ecc.
In effetti, l' impossibilità di Ref Count GC di recuperare le strutture di loop (in generale) può essere vista come una perdita di memoria . Non ci si può aspettare che tutti i GC evitino perdite di memoria. Dipende dall'algoritmo GC e dalle informazioni sulla struttura del tipo disponibili dinamicamente (ad esempio nel
GC conservativo ).
Tracciamento dei netturbini
La famiglia più potente di GC, senza tali perdite, è la famiglia di tracciamento che esplora le parti vive della memoria, a partire da puntatori radice ben identificati. Tutte le parti della memoria che non sono state visitate in questo processo di tracciamento (che può essere effettivamente decomposto in vari modi, ma che devo semplificare) sono parti inutilizzate della memoria che possono essere così recuperate 1 . Questi collezionisti recupereranno tutte le parti di memoria a cui il programma non può più accedere, indipendentemente da ciò che fa. Recupera strutture circolari e i GC più avanzati si basano su alcune variazioni di questo paradigma, a volte altamente sofisticate. Può essere combinato con il conteggio dei riferimenti in alcuni casi e compensare i suoi punti deboli.
Un problema è che la tua affermazione (alla fine della domanda):
Le lingue che offrono la raccolta automatica dei rifiuti sembrano essere i primi candidati a supportare la distruzione / finalizzazione degli oggetti come sanno con certezza al 100% quando un oggetto non è più in uso.
è tecnicamente errato per la tracciabilità dei collezionisti.
Ciò che è noto con certezza al 100% è quali parti della memoria non sono più in uso . (Più precisamente, si dovrebbe dire che non sono più accessibili , perché alcune parti, che non possono più essere utilizzate secondo la logica del programma, sono ancora considerate in uso se nel programma è ancora presente un puntatore inutile dati.) Ma sono necessarie ulteriori elaborazioni e strutture appropriate per sapere quali oggetti inutilizzati potrebbero essere stati archiviati in queste parti della memoria ormai inutilizzate . Ciò non può essere determinato da ciò che è noto del programma, poiché il programma non è più collegato a queste parti della memoria.
Quindi dopo un passaggio di Garbage Collection, ti rimangono frammenti di memoria che contengono oggetti che non sono più in uso, ma a priori non c'è modo di sapere quali siano questi oggetti in modo da applicare la corretta finalizzazione. Inoltre, se il raccoglitore di tracciamento è di tipo mark-and-sweep, è possibile che alcuni frammenti possano contenere oggetti che sono già stati finalizzati in un precedente passaggio GC, ma che non sono stati utilizzati da allora per motivi di frammentazione. Tuttavia, ciò può essere risolto utilizzando la digitazione esplicita estesa.
Mentre un semplice raccoglitore dovrebbe semplicemente recuperare questi frammenti di memoria, senza ulteriori indugi, la finalizzazione richiede un passaggio specifico per esplorare quella memoria inutilizzata, identificare gli oggetti in essa contenuti e applicare le procedure di finalizzazione. Ma una tale esplorazione richiede la determinazione del tipo di oggetti che sono stati memorizzati lì, e la determinazione del tipo è anche necessaria per applicare l'eventuale finalizzazione corretta.
Ciò implica costi aggiuntivi in termini di tempo GC (il passaggio aggiuntivo) e, eventualmente, costi di memoria aggiuntivi per rendere disponibili le informazioni sul tipo corretto durante tale passaggio mediante diverse tecniche. Questi costi possono essere significativi in quanto si vorrà spesso finalizzare solo pochi oggetti, mentre il tempo e lo spazio in alto potrebbero riguardare tutti gli oggetti.
Un altro punto è che il sovraccarico di tempo e spazio può riguardare l'esecuzione del codice del programma e non solo l'esecuzione del GC.
Non posso dare una risposta più precisa, indicando questioni specifiche, perché non conosco i dettagli di molte delle lingue che elenchi. Nel caso di C, la digitazione è un problema molto difficile che porta allo sviluppo di collezionisti conservatori. La mia ipotesi sarebbe che questo influisca anche sul C ++, ma non sono un esperto di C ++. Ciò sembra essere confermato da Hans Boehm che ha svolto gran parte delle ricerche sul GC conservatore. Conservative GC non può recuperare sistematicamente tutta la memoria inutilizzata proprio perché potrebbe non disporre di informazioni precise sul tipo di dati. Per lo stesso motivo, non sarebbe in grado di applicare sistematicamente le procedure di finalizzazione.
Quindi, è possibile fare quello che stai chiedendo, come sai da alcune lingue. Ma non viene gratis. A seconda della lingua e della sua implementazione, può comportare un costo anche quando non si utilizza la funzione. Varie tecniche e compromessi possono essere considerati per affrontare questi problemi, ma questo va oltre lo scopo di una risposta di dimensioni ragionevoli.
1 - questa è una presentazione astratta della raccolta di tracce (che comprende sia GC copia e contrassegna e trascina), le cose variano in base al tipo di raccoglitore di tracce, ed esplorare la parte inutilizzata della memoria è diverso, a seconda se copia o contrassegna e viene utilizzato lo sweep.
finalize
/destroy
è una bugia? Non vi è alcuna garanzia che verrà mai eseguito. E, anche se, non sai quando (data la garbage collection automatica), e se il contesto necessario è ancora lì (potrebbe essere già stato raccolto). Quindi è più sicuro garantire uno stato coerente in altri modi e si potrebbe voler forzare il programmatore a farlo.