2013-05-01 15 views
9

Questa è una domanda molto semplice. Lo formulerò usando C++ e Java, ma è davvero indipendente dalla lingua. consideri un problema ben noto in C++:Garbage Collection vs gestione manuale della memoria

struct Obj 
{ 
    boost::shared_ptr<Obj> m_field; 
}; 

{ 
    boost::shared_ptr<Obj> obj1(new Obj); 
    boost::shared_ptr<Obj> obj2(new Obj); 
    obj1->m_field = obj2; 
    obj2->m_field = obj1; 
} 

Si tratta di una perdita di memoria, e lo sanno tutti :). La soluzione è anche nota: si dovrebbero usare i puntatori deboli per rompere il "blocco del conto alla rovescia". È anche noto che questo problema non può essere risolto automaticamente in linea di principio. È unicamente responsabilità del programmatore risolverlo.

Ma c'è una cosa positiva: un programmatore ha il pieno controllo sui valori degli account. Posso mettere in pausa il mio programma nel debugger ed esaminare il conto per obj1, obj2 e capire che c'è un problema. Posso anche impostare un punto di interruzione in distruttore di un oggetto e osservare un momento di distruzione (o scoprire che l'oggetto non è stato distrutto).

La mia domanda riguarda Java, C#, ActionScript e altre lingue "Garbage Collection". Potrei mancare qualcosa, ma a mio parere

  1. non farmi esaminare refcount di oggetti
  2. Non fatemi sapere quando l'oggetto viene distrutto (va bene, quando l'oggetto è esposto a GC)

Ho spesso sentito dire che questi linguaggi non consentono a un programmatore di perdere una memoria ed è per questo che sono fantastici. Per quanto ho capito, nascondono solo problemi di gestione della memoria e rendono difficile risolverli.

Infine, le domande stesse:

Java:

public class Obj 
{ 
    public Obj m_field; 
} 

{ 
    Obj obj1 = new Obj(); 
    Obj obj2 = new Obj(); 
    obj1.m_field = obj2; 
    obj2.m_field = obj1; 
} 
  1. E 'perdita di memoria?
  2. Se sì: come faccio a rilevare e risolvere il problema?
  3. Se no: perché?
+1

Non è una perdita di memoria. Non ** ti protegge da perdite di memoria, ma non c'è nulla che ti impedisca di rilasciare quegli oggetti nel distruttore. La gestione della memoria fa parte del ** design dell'applicazione **; gli hack di basso livello non compenseranno la mancanza di design. –

+0

non consentire a un programmatore di perdere una memoria, non è il caso, ma questi linguaggi potrebbero proteggerti dalla perdita di memoria nella maggior parte dei casi.Questo è un grande vantaggio per quei programmatori che non hanno alcuna idea della memoria, abbiamo almeno non devi preoccuparti troppo della perdita di memoria quando assegni loro alcuni piccoli progetti – StereoMatching

+0

Non c'è accesso ai refcounts perché la maggior parte delle implementazioni non gestisce i refcounts, e le lingue in generale fanno attenzione a non imporre restrizioni su più dettagli di implementazione di quanto assolutamente necessario (come questo impedisce migliori implementazioni - più veloce, più robusto, più utile, ecc.). – delnan

risposta

8

I sistemi di memoria gestiti si basano sul presupposto che non si desidera tracciare il problema di perdita di memoria in primo luogo. Invece di renderli più facili da risolvere, cerca di assicurarti che non si verifichino mai in primo luogo.

Java ha un termine di perdita per "Perdita di memoria" che significa qualsiasi aumento della memoria che potrebbe influire sull'applicazione, ma non è mai un punto che la memoria gestita non possa ripulire tutta la memoria.

JVM non utilizzare il conteggio di riferimento per una serie di motivi

  • non può trattati i riferimenti circolari come avete osservato.
  • ha una memoria significativa e un sovraccarico di threading per il mantenimento accurato.
  • ci sono modi molto migliori e più semplici per gestire tali situazioni per la memoria gestita.

Mentre il JLS non vieta l'uso di conteggi di riferimento, non viene utilizzato in qualsiasi JVM per quanto ne so.

Invece Java tiene traccia di un numero di contesti radice (ad esempio ogni stack di thread) e può tracciare quali oggetti devono essere conservati e quali possono essere scartati in base al fatto che tali oggetti siano fortemente raggiungibili. Fornisce inoltre la possibilità di riferimenti deboli (che vengono mantenuti purché gli oggetti non vengano ripuliti) e riferimenti morbidi (che non sono generalmente ripuliti ma possono essere a discrezione dei netturbini)

5

AFAIK, Java GC funziona partendo da una serie di riferimenti iniziali ben definiti e calcolando una chiusura transitiva di oggetti che è possibile raggiungere da questi riferimenti. Tutto ciò che non è raggiungibile è "trapelato" e può essere GC-ed.

+0

Forse sono ben definiti per Java, ma non per me :). Dove posso saperne di più su questi riferimenti? – Nick

+0

@Nick Non lavoro in Java, quindi sfortunatamente non lo so. Prova la documentazione Java o lo zio di Google. – Angew

+0

Il riferimento di base per la raccolta dei dati obsoleti in generale è [_Il manuale della raccolta dei rifiuti_] (http://gchandbook.org/) di Richard Jones, Antony Hosking e Eliot Moss. Ha ampie discussioni sulla maggior parte, se non tutte, delle varie strategie di gestione della memoria (incluso il conteggio dei riferimenti usato da shared_ptr). –

1

La differenza fondamentale è che in Java ecc. non si è coinvolti nel problema di smaltimento del tutto. Questo può sembrare una posizione piuttosto spaventosa ma è sorprendentemente utile. Tutte le decisioni che dovevi fare in merito a chi è responsabile dello smaltimento di un oggetto creato sono sparite.

In realtà ha senso. Il sistema sa molto di più su ciò che è raggiungibile e ciò che non è di te. Può anche prendere decisioni molto più flessibili e intelligenti su quando demolire le strutture, ecc.

In sostanza, in questo ambiente è possibile manipolare gli oggetti in un modo molto più complesso senza preoccuparsi di eliminarne uno. L'unica cosa di cui ora devi preoccuparti è se per sbaglio ne incolli uno al soffitto.

Mentre un programmatore ex C si è trasferito a Java, sento il tuo dolore.

Re - la tua domanda finale - non è una perdita di memoria.Quando GC dà il via a , tutto il file viene eliminato tranne ciò che è raggiungibile. In questo caso, supponendo che tu abbia rilasciato obj1 e obj2, nessuno dei due è raggiungibile e quindi entrambi verranno scartati.

+0

"Il sistema sa molto di più su ciò che è raggiungibile e ciò che non è di te". Hmmm ... Suona bene fino a quando non ho problemi di memoria. Ma cosa dovrei fare quando il mio programma ha esaurito la memoria? So come indagare e ottimizzare la gestione della memoria in ambiente "C++ like". Ma cosa dovrei fare in Java? – Nick

+0

@Nick In Java è necessario a) aumentare la quantità di memoria o b) ottimizzare il programma per utilizzare meno memoria. È possibile utilizzare un profiler di memoria in entrambi i casi per determinare qual è la soluzione migliore. Java viene fornito con un VisualVM gratuito che è possibile allegare a qualsiasi processo in esecuzione e, anche se non è eccezionale, è un buon punto di partenza. Ottimizzare l'utilizzo della memoria è che Java è a) più semplice di come molti dei problemi in C++ semplicemente non accadono o b) molto più difficile perché non hai tanti modi per spremere ogni ultimo byte del tuo sistema. Dato che comprate 32 GB per $ 300, vado con l'opzione a) –

1

La raccolta dei rifiuti non è semplice conteggio ref.

L'esempio di riferimento circolare che si dimostra non si verificherà in un linguaggio gestito con garbage collection poiché il garbage collector desidera tracciare i riferimenti di allocazione fino a qualcosa sullo stack. Se non c'è un riferimento allo stack da qualche parte è spazzatura. I sistemi di conteggio dei ref come shared_ptr non sono così intelligenti ed è possibile (come si dimostra) avere due oggetti da qualche parte nell'heap che si mantengono l'un l'altro dall'essere cancellati.

0

Le lingue raccolte dai rifiuti non consentono di ispezionare il referente perché non hanno nessuno. La raccolta dei rifiuti è una cosa completamente diversa dalla gestione della memoria conteggiata. La vera differenza è nel determinismo.

{ 
std::fstream file("example.txt"); 
// do something with file 
} 
// ... later on 
{ 
std::fstream file("example.txt"); 
// do something else with file 
} 

in C++ si ha la garanzia che example.txt è stato chiuso dopo il primo blocco è chiuso, o se viene generata un'eccezione. Caomparing con Java

{ 
try 
    { 
    FileInputStream file = new FileInputStream("example.txt"); 
    // do something with file 
    } 
finally 
    { 
    if(file != null) 
    file.close(); 
    } 
} 
// ..later on 
{ 
try 
    { 
    FileInputStream file = new FileInputStream("example.txt"); 
    // do something with file 
    } 
finally 
    { 
    if(file != null) 
    file.close(); 
    } 
} 

Come si vede, si è scambiato gestione della memoria per tutti gli altri la gestione delle risorse. Questa è la vera differenza, gli oggetti conteggiati mantengono ancora distruzione deterministica. Nei linguaggi di garbage collection è necessario rilasciare manualmente le risorse e verificare l'eccezione. Si potrebbe obiettare che la gestione esplicita della memoria può essere noiosa e soggetta a errori, ma nel C++ moderno è mitigata da puntatori intelligenti e contenitori standard. Hai ancora alcune responsabilità (riferimenti circolari, ad esempio), ma pensa a quanti blocchi catch/finally puoi evitare usando la distruzione deterministica e quanto digitando un Java/C#/etc.il programmatore deve invece fare (dato che devono chiudere/rilasciare manualmente risorse diverse dalla memoria). E so che sta usando la sintassi in C# (e qualcosa di simile nel più recente Java) ma copre solo la durata dell'ambito del blocco e non il problema più generale della proprietà condivisa.

2

Java ha una strategia di gestione della memoria unica. Tutto (tranne alcune cose specifiche) sono allocate nell'heap e non vengono liberate fino a quando il GC non funziona.

Ad esempio:

public class Obj { 
    public Object example; 
    public Obj m_field; 
} 

public static void main(String[] args) { 
    int lastPrime = 2; 
    while (true) { 
     Obj obj1 = new Obj(); 
     Obj obj2 = new Obj(); 
     obj1.example = new Object(); 
     obj1.m_field = obj2; 
     obj2.m_field = obj1; 
     int prime = lastPrime++; 
     while (!isPrime(prime)) { 
      prime++; 
     } 
     lastPrime = prime; 
     System.out.println("Found a prime: " + prime); 
    } 
} 

C gestisce questa situazione che richiede di liberare manualmente la memoria di entrambi 'obj', e C++ conta i riferimenti a 'obj' e automaticamente li distrugge quando escono di portata . Java non non libera questa memoria, almeno non all'inizio.

Il runtime Java attende un po 'di tempo finché sembra che ci sia molta memoria in uso. Dopodiché entra in gioco il garbage collector.

Diciamo che il garbage collector java decide di pulire dopo la 10.000a iterazione del ciclo esterno. A questo punto sono stati creati 10.000 oggetti (che sarebbero già stati liberati in C/C++).

Anche se ci sono 10.000 iterazioni del ciclo esterno, solo il nuovo creato obj1 e obj2 potrebbero essere referenziati dal codice.

Queste sono le "radici" del GC, che java utilizza per trovare tutti gli oggetti a cui è possibile fare riferimento. Il garbage collector esegue iteramente in modo ricorsivo lungo l'albero degli oggetti, contrassegnando 'esempio' come attivo nella dipendenza dalle radici del garbage collector.

Tutti gli altri oggetti vengono quindi distrutti dal garbage collector. Questo comporta una penalizzazione delle prestazioni, ma questo processo è stato fortemente ottimizzato e non è significativo per la maggior parte delle applicazioni.

A differenza di C++, non è necessario preoccuparsi dei cicli di riferimento su tutti, dal momento che vivranno solo gli oggetti raggiungibili dalle radici del GC.

Con le applicazioni Java si do si deve preoccupare della memoria (le liste di pensiero si aggrappano agli oggetti da tutte le iterazioni), ma non è così significativa come in altre lingue.

Per quanto riguarda il debugging: l'idea di Java di debug valori di memoria ad alta utilizza uno speciale 'di memoria-analyzer' per scoprire quali oggetti sono ancora sul mucchio, non preoccuparsi di ciò che fa riferimento quello.

+4

La strategia di gestione della memoria di Java non è propriamente unica. La raccolta dei rifiuti risale al Lisp. Inoltre, diverse JVM possono implementare la garbage collection in modo diverso. –

+0

@JayElston Lisp ha inventato la raccolta dei rifiuti? E so che la sua semplificazione, JVM hanno un sacco di margine di manovra sul howto raccogliere CRUD :) – Techcable

+0

Se credi [Wikipedia] (https://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29) :-) –