Warum erstreckt sich die Speicherbereinigung nur auf den Speicher und nicht auf andere Ressourcentypen?


12

Es scheint, als hätten die Leute die manuelle Speicherverwaltung satt, also haben sie die Müllabfuhr erfunden, und das Leben war einigermaßen gut. Aber was ist mit allen anderen Ressourcentypen? Dateideskriptoren, Sockets oder sogar vom Benutzer erstellte Daten wie Datenbankverbindungen?

Das fühlt sich wie eine naive Frage an, aber ich kann keinen Ort finden, an dem jemand sie gestellt hat. Betrachten wir Dateideskriptoren. Angenommen, ein Programm weiß, dass es beim Start nur 4000 fds zur Verfügung haben darf. Wann immer es eine Operation ausführt, die einen Dateideskriptor öffnet, was wäre, wenn dies der Fall wäre

  1. Stellen Sie sicher, dass es nicht zu Ende geht.
  2. Wenn dies der Fall ist, lösen Sie den Garbage Collector aus, wodurch eine Menge Speicher freigegeben wird.
  3. Wenn ein Teil des freigegebenen Speichers Verweise auf Dateideskriptoren enthält, schließen Sie diese sofort. Es weiß, dass der Speicher zu einer Ressource gehört, da der mit dieser Ressource verknüpfte Speicher beim ersten Öffnen mangels eines besseren Begriffs in einer 'Dateideskriptor-Registrierung' registriert wurde.
  4. Öffnen Sie einen neuen Dateideskriptor, kopieren Sie ihn in einen neuen Speicher, registrieren Sie diesen Speicherort in der 'Dateideskriptor-Registrierung' und geben Sie ihn an den Benutzer zurück.

Die Ressource würde also nicht sofort freigegeben, sondern immer dann freigegeben, wenn der gc ausgeführt wurde, was zumindest kurz vor dem Auslaufen der Ressource umfasst, vorausgesetzt, sie wird nicht vollständig genutzt.

Und es scheint, dass dies für viele benutzerdefinierte Probleme bei der Bereinigung von Ressourcen ausreichen würde. Es ist mir gelungen, hier einen einzigen Kommentar zu finden, der darauf verweist, dass eine ähnliche Bereinigung in C ++ mit einem Thread durchgeführt wird, der einen Verweis auf eine Ressource enthält, und ihn bereinigt, wenn nur noch ein einziger Verweis übrig ist (aus dem Bereinigungsthread), aber ich kann ' Es gibt keine Beweise dafür, dass es sich um eine Bibliothek oder einen Teil einer vorhandenen Sprache handelt.

Antworten:


4

GC befasst sich mit einer vorhersehbaren und reservierten Ressource. Die VM hat die vollständige Kontrolle darüber und die vollständige Kontrolle darüber, welche Instanzen wann erstellt werden. Die Schlüsselwörter hier sind "reserviert" und "totale Kontrolle". Handles werden vom Betriebssystem zugewiesen, und Zeiger sind ... Well-Zeiger auf Ressourcen, die außerhalb des verwalteten Bereichs zugewiesen sind. Aus diesem Grund sind Handles und Zeiger nicht auf die Verwendung in verwaltetem Code beschränkt. Sie können und werden häufig von verwaltetem und nicht verwaltetem Code verwendet, der im selben Prozess ausgeführt wird.

Ein "Ressourcensammler" kann überprüfen, ob ein Handle / Zeiger in einem verwalteten Bereich verwendet wird oder nicht, aber er weiß per Definition nicht, was außerhalb seines Speicherbereichs geschieht (und um die Sache noch schlimmer zu machen, können einige Handles verwendet werden über Prozessgrenzen hinweg).

Ein praktisches Beispiel ist die .NET CLR. Man kann aromatisiertes C ++ verwenden, um Code zu schreiben, der sowohl mit verwalteten als auch mit nicht verwalteten Speicherbereichen funktioniert. Handles, Zeiger und Referenzen können zwischen verwaltetem und nicht verwaltetem Code weitergegeben werden. Der nicht verwaltete Code muss spezielle Konstrukte / Typen verwenden, damit die CLR weiterhin Verweise auf verwaltete Ressourcen verfolgen kann. Aber das ist das Beste, was es tun kann. Dies kann mit Handles und Zeigern nicht dasselbe tun. Aus diesem Grund würde der Resource Collector nicht wissen, ob es in Ordnung ist, ein bestimmtes Handle oder einen bestimmten Zeiger freizugeben.

Bearbeiten: In Bezug auf die .NET CLR habe ich keine Erfahrung mit der C ++ - Entwicklung mit der .NET-Plattform. Möglicherweise gibt es spezielle Mechanismen, mit denen die CLR Verweise auf Handles / Zeiger zwischen verwaltetem und nicht verwaltetem Code verfolgen kann. Wenn dies der Fall ist, könnte sich die CLR um die Lebensdauer dieser Ressourcen kümmern und sie freigeben, wenn alle Verweise auf sie gelöscht sind (zumindest in einigen Szenarien). In beiden Fällen schreiben Best Practices vor, dass Handles (insbesondere solche, die auf Dateien verweisen) und Zeiger freigegeben werden, sobald sie nicht benötigt werden. Ein Ressourcensammler würde dies nicht einhalten, das ist ein weiterer Grund, keinen zu haben.

Bearbeiten 2: Für CLR / JVM / VMs im Allgemeinen ist es relativ trivial, Code zu schreiben, um ein bestimmtes Handle freizugeben, wenn es nur im verwalteten Bereich verwendet wird. In .NET wäre so etwas wie:

// 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

Dies scheint einer der Gründe zu sein, warum Sprachen mit Garbage Collectors Finalizer implementieren. Finalizer sollen es einem Programmierer ermöglichen, die Ressourcen eines Objekts während der Speicherbereinigung zu bereinigen. Das große Problem bei Finalisierern ist, dass ihre Ausführung nicht garantiert ist.

Es gibt hier eine ziemlich gute Beschreibung der Verwendung von Finalisierern:

Objektabschluss und Bereinigung

Tatsächlich wird speziell der Dateideskriptor als Beispiel verwendet. Sie sollten sicherstellen, dass Sie diese Ressource selbst bereinigen. Es gibt jedoch einen Mechanismus, mit dem möglicherweise Ressourcen wiederhergestellt werden können, die nicht ordnungsgemäß freigegeben wurden.


Ich bin mir nicht sicher, ob dies meine Frage beantwortet. Es fehlt der Teil meines Vorschlags, in dem das System weiß, dass eine Ressource knapp wird. Die einzige Möglichkeit, diesen Teil einzuschlagen, besteht darin, sicherzustellen, dass Sie den gc manuell ausführen, bevor Sie neue Dateideskriptoren zuweisen. Dies ist jedoch äußerst ineffizient, und ich weiß nicht, ob Sie den gc überhaupt in Java ausführen können.
Mindreader

OK, aber Dateideskriptoren stellen normalerweise eine geöffnete Datei im Betriebssystem dar, die (abhängig vom Betriebssystem) die Verwendung von Ressourcen auf Systemebene wie Sperren, Pufferpools, Strukturpools usw. impliziert. Ehrlich gesagt sehe ich keinen Vorteil darin, diese Strukturen für eine spätere Müllabfuhr offen zu lassen, und ich sehe viele Nachteile darin, sie länger als nötig zugewiesen zu lassen. Die Finalize () -Methoden sollen eine letzte Bereinigung des Grabens ermöglichen, falls ein Programmierer Aufrufe zum Bereinigen von Ressourcen übersieht, auf die man sich jedoch nicht verlassen sollte.
Brian Hibbert

Ich verstehe, dass der Grund, auf den man sich nicht verlassen sollte, darin besteht, dass Sie möglicherweise zu viele Dateien öffnen, bevor der GC passiert, wenn Sie eine Tonne dieser Ressourcen zuweisen, z. B. wenn Sie eine Dateihierarchie absteigen, in der jede Datei geöffnet wird laufen, was zu einer Explosion führt. Dasselbe würde mit dem Speicher passieren, außer dass die Laufzeit überprüft, ob der Speicher nicht knapp wird. Ich würde gerne wissen, warum ein System nicht implementiert werden kann, um beliebige Ressourcen vor dem Aufblasen zurückzugewinnen, fast genauso wie Speicher.
Mindreader

Ein System KANN in andere GC-Ressourcen als den Speicher geschrieben werden, aber Sie müssten die Referenzzähler verfolgen oder eine andere Methode haben, um festzustellen, wann eine Ressource nicht mehr verwendet wird. Sie möchten die Freigabe und Neuzuweisung von Ressourcen, die noch verwendet werden, NICHT freigeben. Alles Chaos kann entstehen, wenn in einem Thread eine Datei zum Schreiben geöffnet ist, das Betriebssystem das Dateihandle "zurückfordert" und ein anderer Thread eine andere Datei zum Schreiben mit demselben Handle öffnet. Und ich würde auch immer noch vorschlagen, dass es eine Verschwendung von erheblichen Ressourcen ist, sie offen zu lassen, bis ein GC-ähnlicher Thread dazu kommt, sie freizugeben.
Brian Hibbert

3

Es gibt viele Programmiertechniken, um diese Art von Ressourcen zu verwalten.

  • C ++ - Programmierer verwenden häufig ein Muster namens Resource Acquisition is Initialization , kurz RAII. Dieses Muster stellt sicher, dass ein Objekt, an dem Ressourcen festgehalten werden, geschlossen wird, wenn es den Gültigkeitsbereich verlässt. Dies ist hilfreich, wenn die Lebensdauer des Objekts einem bestimmten Bereich im Programm entspricht (z. B. wenn es mit der Zeit übereinstimmt, zu der ein bestimmter Stapelrahmen auf dem Stapel vorhanden ist). Daher ist es hilfreich für Objekte, auf die lokale Variablen zeigen (Zeiger) auf dem Stapel gespeicherte Variablen), aber nicht so hilfreich für Objekte, auf die Zeiger zeigen, die auf dem Heap gespeichert sind.

  • Java, C # und viele andere Sprachen bieten eine Möglichkeit, eine Methode anzugeben, die aufgerufen wird, wenn ein Objekt nicht mehr aktiv ist und vom Garbage Collector erfasst werden soll. Siehe z. B. Finalizer dispose()und andere. Die Idee ist, dass der Programmierer eine solche Methode implementieren kann, so dass die Ressource explizit geschlossen wird, bevor das Objekt vom Garbage Collector freigegeben wird. Diese Ansätze weisen jedoch einige Probleme auf, über die Sie an anderer Stelle lesen können. Beispielsweise sammelt der Garbage Collector das Objekt möglicherweise erst viel später als gewünscht.

  • C # und andere Sprachen bieten ein usingSchlüsselwort, mit dem sichergestellt wird, dass Ressourcen geschlossen werden, nachdem sie nicht mehr benötigt werden (Sie vergessen also nicht, den Dateideskriptor oder eine andere Ressource zu schließen). Dies ist oft besser, als sich auf den Garbage Collector zu verlassen, um festzustellen, dass das Objekt nicht mehr aktiv ist. Siehe z . B. /programming//q/75401/781723 . Der allgemeine Begriff hier ist eine verwaltete Ressource . Diese Vorstellung baut auf RAII und Finalisierern auf und verbessert sie in gewisser Weise.


Ich bin weniger an einer sofortigen Freigabe von Ressourcen interessiert als vielmehr an der Idee einer zeitnahen Freigabe. RIAA ist großartig, aber nicht sehr gut für sehr viele Garbage Collection-Sprachen geeignet. Java fehlt die Fähigkeit zu wissen, wann eine bestimmte Ressource knapp wird. Operationen mit und ohne Klammern sind nützlich und behandeln Fehler, aber ich bin nicht an ihnen interessiert. Ich möchte einfach nur Ressourcen zuweisen, und dann bereinigen sie sich, wann immer es zweckmäßig oder notwendig ist, und es gibt kaum eine Möglichkeit, dies zu vermasseln. Ich denke, niemand hat sich wirklich damit befasst.
Mindreader

2

Alle Speicher sind gleich, wenn ich nach 1K frage, ist es mir egal, woher im Adressraum die 1K kommt.

Wenn ich nach einem Dateihandle frage, möchte ich ein Handle für die Datei, die ich öffnen möchte. Wenn ein Dateihandle für eine Datei geöffnet ist, wird häufig der Zugriff anderer Prozesse oder Computer auf die Datei blockiert.

Daher müssen Dateihandles geschlossen werden, sobald sie nicht benötigt werden. Andernfalls blockieren sie andere Zugriffe auf die Datei, aber der Speicher muss nur zurückgefordert werden, wenn Ihnen die Datei ausgeht.

Das Ausführen eines GC-Durchlaufs ist kostspielig und wird nur „bei Bedarf“ durchgeführt. Es ist nicht möglich vorherzusagen, wann ein anderer Prozess ein Dateihandle benötigt, das Ihr Prozess möglicherweise nicht mehr verwendet, aber noch geöffnet hat.


Ihre Antwort trifft den eigentlichen Schlüssel: Der Speicher ist fungibel und die meisten Systeme haben genug, dass er nicht besonders schnell zurückgefordert werden muss. Wenn ein Programm dagegen exklusiven Zugriff auf eine Datei erhält, werden alle anderen Programme im gesamten Universum blockiert, die diese Datei möglicherweise verwenden müssen, unabhängig davon, wie viele andere Dateien vorhanden sind.
Supercat

0

Ich würde vermuten, dass der Grund, warum dies für andere Ressourcen nicht viel angegangen wurde, genau darin liegt, dass die meisten anderen Ressourcen bevorzugt so schnell wie möglich freigegeben werden, damit jeder sie wiederverwenden kann.

Beachten Sie natürlich, dass Ihr Beispiel jetzt unter Verwendung von "schwachen" Dateideskriptoren mit vorhandenen GC-Techniken bereitgestellt werden kann.


0

Es ist ziemlich einfach zu überprüfen, ob der Speicher nicht mehr verfügbar ist (und somit garantiert nicht mehr verwendet wird). Die meisten anderen Arten von Ressourcen können mit mehr oder weniger denselben Techniken verarbeitet werden (dh Ressourcenerfassung ist Initialisierung, RAII und das Gegenstück zur Freigabe, wenn der Benutzer zerstört wird, was ihn mit der Speicherverwaltung verbindet). Eine Art "Just-in-Time" -Freigabe ist im Allgemeinen nicht möglich (überprüfen Sie das Stoppproblem, Sie müssten herausfinden, dass zum letzten Mal eine Ressource verwendet wurde). Ja, manchmal kann es automatisch gemacht werden, aber es ist ein viel chaotischerer Fall als Speicher. Daher ist es größtenteils auf Benutzereingriffe angewiesen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.