Auf der Suche nach Einsichten in Entscheidungen rund um müllsammelndes Sprachdesign. Vielleicht könnte mich ein Sprachexperte aufklären? Ich komme aus einem C ++ - Hintergrund, daher ist dieser Bereich für mich verwirrend.
Es scheint, dass fast alle modernen Sprachen mit OOPy-Objektunterstützung wie Ruby, Javascript / ES6 / ES7, Actionscript, Lua usw. das Destruktor- / Finalisierungsparadigma vollständig weglassen. Python scheint die einzige mit seiner class __del__()
Methode zu sein. Warum ist das? Gibt es funktionale / theoretische Einschränkungen in Sprachen mit automatischer Speicherbereinigung, die eine effektive Implementierung einer Destruktor- / Finalisierungsmethode für Objekte verhindern?
Ich finde es äußerst mangelhaft, dass diese Sprachen das Gedächtnis als die einzige Ressource betrachten, die es wert ist, verwaltet zu werden. Was ist mit Sockets, Dateizugriffsnummern und Anwendungsstatus? Ohne die Möglichkeit, benutzerdefinierte Logik zum Bereinigen von Nicht-Speicherressourcen und Zuständen bei der Objekt-Finalisierung zu implementieren, muss ich meine Anwendung mit benutzerdefinierten myObject.destroy()
Stilaufrufen überschütten, die Bereinigungslogik außerhalb meiner "Klasse" platzieren, den Verkapselungsversuch unterbrechen und meine ablegen Anwendung auf Ressourcenlecks aufgrund von menschlichem Versagen anstatt automatisch vom gc behandelt zu werden.
Was sind die Entscheidungen zum Sprachdesign, die dazu führen, dass diese Sprachen keine Möglichkeit haben, benutzerdefinierte Logik für die Objektentsorgung auszuführen? Ich muss mir vorstellen, dass es einen guten Grund gibt. Ich möchte die technischen und theoretischen Entscheidungen, die dazu geführt haben, dass diese Sprachen die Zerstörung / Finalisierung von Objekten nicht unterstützen, besser verstehen.
Aktualisieren:
Vielleicht eine bessere Möglichkeit, meine Frage zu formulieren:
Warum sollte eine Sprache das eingebaute Konzept von Objektinstanzen mit Klassen oder klassenähnlichen Strukturen zusammen mit benutzerdefinierten Instanzen (Konstruktoren) haben und dennoch die Zerstörungs- / Finalisierungsfunktionalität vollständig weglassen? Sprachen, die eine automatische Speicherbereinigung anbieten, scheinen die Hauptkandidaten für die Zerstörung / Finalisierung von Objekten zu sein, da sie mit 100% iger Sicherheit wissen, wann ein Objekt nicht mehr verwendet wird. Die meisten dieser Sprachen unterstützen dies jedoch nicht.
Ich denke nicht, dass es ein Fall ist, in dem der Destruktor niemals aufgerufen wird, da dies ein Kernspeicherverlust wäre, den gcs vermeiden soll. Ich konnte ein mögliches Argument dafür sehen, dass der Destruktor / Finalizer möglicherweise erst zu einem unbestimmten Zeitpunkt in der Zukunft aufgerufen wird, aber das hat Java oder Python nicht davon abgehalten, die Funktionalität zu unterstützen.
Was sind die Hauptgründe für das Design von Sprachen, um keine Form der Objekt-Finalisierung zu unterstützen?
finalize
/destroy
eine Lüge ist? Es gibt keine Garantie, dass es jemals ausgeführt wird. Und selbst wenn Sie nicht wissen, wann (bei gegebener automatischer Garbage Collection) und falls erforderlich, immer noch Kontext vorhanden ist (möglicherweise wurde er bereits gesammelt). Es ist also sicherer, einen konsistenten Zustand auf andere Weise sicherzustellen, und man möchte den Programmierer möglicherweise dazu zwingen.Antworten:
Das Muster, über das Sie sprechen, bei dem Objekte wissen, wie sie ihre Ressourcen bereinigen, lässt sich in drei relevante Kategorien unterteilen. Lassen Sie uns Destruktoren nicht mit Finalisierern in Konflikt bringen - nur eine ist mit der Garbage Collection verbunden:
Die vom Programmierer automatisch festgelegte Aufräummethode finalizer pattern : cleanup wird automatisch aufgerufen.
Finalizer werden vor der Freigabe durch einen Garbage Collector automatisch aufgerufen. Der Begriff gilt, wenn der verwendete Speicherbereinigungsalgorithmus Objektlebenszyklen bestimmen kann.
Die vom Programmierer automatisch deklarierte Bereinigungsmethode destructor pattern : wird nur manchmal automatisch aufgerufen.
Destruktoren können für vom Stapel zugewiesene Objekte automatisch aufgerufen werden (da die Objektlebensdauer deterministisch ist), müssen jedoch explizit für alle möglichen Ausführungspfade für vom Heap zugewiesene Objekte aufgerufen werden (da die Objektlebensdauer nicht deterministisch ist).
Die vom Programmierer deklarierte, definierte und aufgerufene Bereinigungsmethode " disposer pattern : cleanup".
Programmierer erstellen eine Entsorgungsmethode und nennen sie selbst - hier liegt Ihre benutzerdefinierte
myObject.destroy()
Methode. Wenn eine Entsorgung unbedingt erforderlich ist, müssen die Entsorger auf allen möglichen Ausführungswegen angerufen werden.Finalizer sind die Droiden, nach denen Sie suchen.
Das Finalizer-Muster (das Muster, nach dem Sie fragen) ist der Mechanismus zum Zuordnen von Objekten zu Systemressourcen (Sockets, Dateideskriptoren usw.) für die gegenseitige Wiederherstellung durch einen Garbage Collector. Finalizer sind jedoch grundsätzlich dem verwendeten Garbage Collection-Algorithmus ausgeliefert.
Betrachten Sie diese Annahme von Ihnen:
Technisch falsch (danke, @babou). Bei der Garbage Collection geht es im Wesentlichen um Speicher, nicht um Objekte. Ob oder wann ein Erfassungsalgorithmus feststellt, dass der Speicher eines Objekts nicht mehr verwendet wird, hängt vom Algorithmus und (möglicherweise) davon ab, wie sich Ihre Objekte aufeinander beziehen. Lassen Sie uns über zwei Arten von Laufzeit-Garbage-Collectors sprechen. Es gibt viele Möglichkeiten, diese zu grundlegenden Techniken zu ändern und zu erweitern:
GC verfolgen. Diese verfolgen Speicher, keine Objekte. Sofern dies nicht erweitert wird, behalten sie keine Rückverweise auf Objekte aus dem Speicher bei. Sofern nicht erweitert, wissen diese GCs nicht, wann ein Objekt finalisiert werden kann, auch wenn sie wissen, wann sein Speicher nicht erreichbar ist. Daher können Finalizer-Aufrufe nicht garantiert werden.
Referenzzählung GC . Diese verwenden Objekte, um den Speicher zu verfolgen. Sie modellieren die Erreichbarkeit von Objekten mit einem gerichteten Referenzdiagramm. Wenn sich in Ihrem Objektreferenzdiagramm ein Zyklus befindet, wird der Finalizer für alle Objekte im Zyklus nie aufgerufen (bis zum Programmabschluss natürlich). Auch hier sind Finalizer-Aufrufe nicht garantiert.
TLDR
Die Müllabfuhr ist schwierig und vielfältig. Ein Finalizer-Aufruf kann vor Programmende nicht garantiert werden.
quelle
finalize()
. Da Java jedoch nicht garantieren konnte, dass der Finalizer vor dem Beenden des Programms aufgerufen wird, konnte Java ihn nicht mehr unterstützen. Wenn Sie nicht sagen, dass Ihre Antwort falsch ist, ist sie möglicherweise unvollständig. Immer noch ein sehr guter Beitrag. Vielen Dank.In einer Nussschale
Finalisierung ist keine einfache Angelegenheit, die von Müllsammlern erledigt werden muss. Die Verwendung mit Referenzzähl-GC ist einfach. Diese GC-Familie ist jedoch häufig unvollständig, sodass Speicherlecks durch explizite Auslösung der Zerstörung und Finalisierung einiger Objekte und Strukturen ausgeglichen werden müssen. Die Verfolgung von Garbage Collectors ist wesentlich effektiver, macht es jedoch schwieriger, zu finalisierende und zu zerstörende Objekte zu identifizieren, als nur den ungenutzten Speicher zu identifizieren. Dies erfordert eine komplexere Verwaltung, kostet Zeit und Platz und ist komplexer die Umsetzung.
Einführung
Ich gehe davon aus, dass Sie nach dem Grund fragen, warum Sprachen mit Speicherbereinigung die Zerstörung / Finalisierung innerhalb des Speicherbereinigungsprozesses nicht automatisch handhaben, wie in der Bemerkung angegeben:
Ich bin mit der akzeptierten Antwort von kdbanman nicht einverstanden . Obwohl die dort genannten Fakten zumeist zutreffend sind, obwohl sie stark auf die Referenzzählung abzielen, glaube ich nicht, dass sie die in der Frage beanstandete Situation richtig erklären.
Ich glaube nicht, dass die in dieser Antwort entwickelte Terminologie ein großes Problem darstellt, und es ist wahrscheinlicher, dass sie die Dinge verwirrt. In der Tat wird die Terminologie, wie dargestellt, hauptsächlich durch die Art und Weise bestimmt, wie die Prozeduren aktiviert werden, und nicht durch die Art und Weise, wie sie ausgeführt werden. Der Punkt ist, dass es in allen Fällen notwendig ist, ein Objekt, das nicht mehr benötigt wird, mit einem Bereinigungsprozess fertigzustellen und alle verwendeten Ressourcen freizugeben, wobei der Speicher nur eine davon ist. Im Idealfall sollte dies alles automatisch erfolgen, wenn das Objekt nicht mehr verwendet werden soll, und zwar mithilfe eines Müllsammlers. In der Praxis kann GC fehlen oder Mängel aufweisen. Dies wird durch die explizite Auslösung durch das Programm für Finalisierung und Rückforderung ausgeglichen.
Explizites Triggern durch das Programm ist ein Problem, da es schwer zu analysierende Programmierfehler ermöglichen kann, wenn ein noch verwendetes Objekt explizit beendet wird.
Daher ist es viel besser, sich auf die automatische Garbage Collection zu verlassen, um Ressourcen zurückzugewinnen. Es gibt jedoch zwei Probleme:
Einige Speicherbereinigungstechniken ermöglichen Speicherlecks, die eine vollständige Rückgewinnung von Ressourcen verhindern. Dies ist allgemein für die Referenzzählung von GC-Daten bekannt, kann jedoch für andere GC-Techniken angezeigt werden, wenn einige Datenorganisationen ohne Sorgfalt verwendet werden (hier nicht besprochener Punkt).
Während die GC-Technik möglicherweise die Identifizierung von nicht mehr verwendeten Speicherressourcen erleichtert, ist das Finalisieren der darin enthaltenen Objekte möglicherweise nicht einfach, und dies erschwert das Problem, andere von diesen Objekten verwendete Ressourcen zurückzugewinnen, was häufig der Zweck der Finalisierung ist.
Schließlich wird häufig vergessen, dass GC-Zyklen durch irgendetwas ausgelöst werden können, nicht nur durch Speichermangel, wenn die richtigen Hooks bereitgestellt werden und die Kosten für einen GC-Zyklus als wertvoll erachtet werden. Daher ist es vollkommen in Ordnung, eine GC einzuleiten, wenn irgendeine Art von Ressource fehlt, in der Hoffnung, eine zu befreien.
Referenzzählung Müllsammler
Die Referenzzählung ist eine schwache Müllsammeltechnik , die Zyklen nicht richtig handhabt. Es wäre in der Tat schwach, veraltete Strukturen zu zerstören und andere Ressourcen zurückzugewinnen, nur weil es schwach ist, Speicher zurückzugewinnen. Finalizer können jedoch am einfachsten mit einem Garbage Collector (GC) mit Referenzzählung verwendet werden, da ein GC mit Referenzzählung eine Struktur zurückerlangt, wenn seine Referenzzählung auf 0 abfällt. Zu diesem Zeitpunkt ist seine Adresse zusammen mit seinem Typ entweder statisch bekannt oder dynamisch. Daher ist es möglich, den Speicher genau nach dem Anwenden des richtigen Finalizers und dem rekursiven Aufrufen des Prozesses auf alle spitzen Objekte (möglicherweise über die Finalisierungsprozedur) wiederzugewinnen.
Zusammenfassend lässt sich sagen, dass die Finalisierung mit Ref Counting GC leicht zu implementieren ist, jedoch unter der "Unvollständigkeit" dieses GC leidet, und zwar aufgrund kreisförmiger Strukturen, und zwar in genau demselben Ausmaß, unter dem die Speicherfreigabe leidet. Mit anderen Worten, mit der Referenzanzahl wird der Speicher genauso schlecht verwaltet wie andere Ressourcen wie Sockets, Dateihandles usw.
Tatsächlich kann die Unfähigkeit von Ref Count GC, Schleifenstrukturen (im Allgemeinen) zurückzugewinnen, als Speicherverlust angesehen werden . Sie können nicht von allen GCs erwarten, dass sie Speicherlecks vermeiden. Dies hängt vom GC-Algorithmus und von den dynamisch verfügbaren Typstrukturinformationen ab (z. B. bei konservativem GC ).
Müllsammler aufspüren
Die leistungsstärkere Familie von GC ohne solche Lecks ist die Verfolgungsfamilie , die die aktiven Teile des Speichers ausgehend von gut identifizierten Stammzeigern untersucht. Alle Teile des Speichers, die in diesem Ablaufverfolgungsprozess nicht besucht werden (die tatsächlich auf verschiedene Arten zerlegt werden können, aber ich muss sie vereinfachen), sind nicht verwendete Teile des Speichers, die auf diese Weise zurückgewonnen werden können 1 . Diese Kollektoren rufen alle Speicherteile ab, auf die das Programm nicht mehr zugreifen kann, unabhängig davon, was es tut. Es werden kreisförmige Strukturen wiederhergestellt, und die fortgeschritteneren GC basieren auf einigen Variationen dieses Paradigmas, die manchmal hochentwickelt sind. In einigen Fällen kann es mit der Referenzzählung kombiniert werden und seine Schwächen ausgleichen.
Ein Problem ist, dass Ihre Aussage (am Ende der Frage):
ist für die Rückverfolgung von Sammlern technisch falsch .
Mit 100% iger Sicherheit ist bekannt, welche Teile des Speichers nicht mehr verwendet werden . (Genauer gesagt, sie sind nicht mehr zugänglich , da einige Teile, die gemäß der Logik des Programms nicht mehr verwendet werden können, weiterhin als verwendet betrachtet werden, wenn das Programm noch einen nutzlosen Zeiger auf sie enthält Daten.) Aber weitere Verarbeitung und geeignete Strukturen sind erforderlich, um zu wissen, welche nicht verwendeten Objekte in diesen jetzt nicht verwendeten Teilen des Speichers gespeichert wurden . Dies kann aus dem, was von dem Programm bekannt ist, nicht bestimmt werden, da das Programm nicht länger mit diesen Teilen des Speichers verbunden ist.
Nach einem Durchlauf der Speicherbereinigung verbleiben also Fragmente des Speichers, die Objekte enthalten, die nicht mehr verwendet werden. Es ist jedoch a priori nicht möglich, diese Objekte zu ermitteln, um die korrekte Finalisierung anzuwenden. Wenn es sich bei dem Ablaufverfolgungskollektor um den Mark-and-Sweep-Typ handelt, können einige der Fragmente Objekte enthalten, die bereits in einem vorherigen GC-Durchgang finalisiert wurden, seitdem jedoch aus Fragmentierungsgründen nicht mehr verwendet wurden. Dies kann jedoch mit erweiterter expliziter Typisierung behoben werden.
Während ein einfacher Kollektor diese Speicherfragmente nur ohne weiteres zurückerobern würde, erfordert die Finalisierung einen bestimmten Durchgang, um den nicht verwendeten Speicher zu untersuchen, die darin enthaltenen Objekte zu identifizieren und Finalisierungsverfahren anzuwenden. Eine solche Untersuchung erfordert jedoch die Bestimmung des Objekttyps, der dort gespeichert wurde, und die Typbestimmung ist auch erforderlich, um gegebenenfalls die ordnungsgemäße Finalisierung durchzuführen.
Dies bedeutet zusätzliche Kosten in der GC-Zeit (der zusätzliche Durchlauf) und möglicherweise zusätzliche Speicherkosten, um während dieses Durchlaufs die richtigen Typinformationen durch verschiedene Techniken verfügbar zu machen. Diese Kosten können erheblich sein, da häufig nur wenige Objekte finalisiert werden sollen, während der zeitliche und räumliche Aufwand alle Objekte betreffen kann.
Ein weiterer Punkt ist, dass der zeitliche und räumliche Aufwand die Ausführung von Programmcode und nicht nur die GC-Ausführung betreffen kann.
Ich kann keine genauere Antwort geben und auf bestimmte Fragen hinweisen, da ich die Besonderheiten vieler der von Ihnen aufgelisteten Sprachen nicht kenne. Im Fall von C ist die Typisierung ein sehr schwieriges Thema, das zur Entwicklung konservativer Sammler führt. Ich vermute, dass dies auch C ++ betrifft, aber ich bin kein Experte für C ++. Dies scheint von Hans Boehm bestätigt zu werden , der einen Großteil der Forschung zur konservativen GC durchgeführt hat. Conservative GC kann nicht systematisch den gesamten nicht verwendeten Speicher zurückfordern, da möglicherweise keine genauen Typinformationen zu Daten vorliegen. Aus dem gleichen Grund wäre es nicht möglich, Finalisierungsverfahren systematisch anzuwenden.
So ist es möglich, das zu tun, wonach Sie fragen, wie Sie es aus einigen Sprachen kennen. Aber es ist nicht kostenlos. Abhängig von der Sprache und ihrer Implementierung kann dies auch dann Kosten verursachen, wenn Sie die Funktion nicht verwenden. Verschiedene Techniken und Kompromisse können in Betracht gezogen werden, um diese Probleme anzugehen, aber das würde den Rahmen einer angemessenen Antwort sprengen.
1 - Dies ist eine abstrakte Darstellung der Ablaufverfolgungssammlung (die sowohl das Kopieren als auch das Mark-and-Sweep-GC umfasst). Die Dinge variieren je nach Typ des Ablaufverfolgungssammlers, und das Erkunden des nicht verwendeten Teils des Speichers ist unterschiedlich, je nachdem, ob Kopieren oder Markieren und Sweep wird verwendet.
quelle
getting memory recycled
, die ich aufrufereclamation
und die vorher eine Bereinigung vornimmt, z. B. das Zurückfordern anderer Ressourcen oder das Aktualisieren einiger Objekttabellen, die ich aufrufefinalization
. Dies schien mir die relevanten Themen zu sein, aber ich habe möglicherweise einen Punkt in Ihrer Terminologie übersehen, der für mich neu war.Das Objektdestruktormuster ist für die Fehlerbehandlung in der Systemprogrammierung von grundlegender Bedeutung, hat jedoch nichts mit der Garbage Collection zu tun. Vielmehr hat es mit der Anpassung der Objektlebensdauer an einen Bereich zu tun und kann in jeder Sprache mit erstklassigen Funktionen implementiert / verwendet werden.
Beispiel (Pseudocode). Angenommen, Sie haben einen Rohdateityp wie den Posix-Dateideskriptortyp. Es gibt vier grundlegende Operationen
open()
,close()
,read()
,write()
. Sie möchten einen "sicheren" Dateityp implementieren, der immer nach sich selbst aufräumt. (Dh das hat einen automatischen Konstruktor und Destruktor.)Ich werde unsere Sprache hat die Ausnahmebehandlung mit übernehmen
throw
,try
undfinally
(in Sprachen ohne Ausnahmebehandlung Sie eine Disziplin einrichten können , wo der Benutzer Ihrer Art einen besonderen Wert gibt einen Fehler anzuzeigen.)Sie richten eine Funktion ein, die eine Funktion akzeptiert, die die Arbeit erledigt. Die Worker-Funktion akzeptiert ein Argument (ein Handle auf die "sichere" Datei).
Sie stellen auch Implementierungen von
read()
undwrite()
fürsafe_file
(die nur dasraw_file
read()
und aufrufenwrite()
) bereit . Der Benutzer verwendet nun den folgendensafe_file
Typ:Ein C ++ - Destruktor ist eigentlich nur syntaktischer Zucker für einen
try-finally
Block. Ich habe hier so ziemlich alles getan, was eine C ++ -safe_file
Klasse mit einem Konstruktor und einem Destruktor kompilieren würde. Beachten Sie, dass C ++ keinefinally
Ausnahmen hat, insbesondere weil Stroustrup der Meinung war, dass die Verwendung eines expliziten Destruktors syntaktisch besser ist (und er hat ihn in die Sprache eingeführt, bevor die Sprache anonyme Funktionen hatte).(Dies ist eine Vereinfachung einer der Methoden, mit denen Menschen seit vielen Jahren Fehlerbehandlungen in Lisp-ähnlichen Sprachen durchführen. Ich glaube, ich bin zum ersten Mal Ende der 1980er oder Anfang der 1990er Jahre darauf gestoßen, aber ich weiß nicht mehr, wo.)
quelle
safe_file
undwith_file_opened_for_read
(ein Objekt, das sich selbst schließt, wenn es außerhalb des Gültigkeitsbereichs liegt) bereitzustellen ). Das ist das Wichtigste, dass es nicht die gleiche Syntax wie Konstruktoren hat, ist irrelevant. Lisp, Scheme, Java, Scala, Go, Haskell, Rust, Javascript und Clojure unterstützen alle ausreichend erstklassige Funktionen, sodass sie keine Destruktoren benötigen, um dieselbe nützliche Funktion bereitzustellen.Dies ist keine vollständige Antwort auf die Frage, aber ich möchte ein paar Bemerkungen hinzufügen, die in den anderen Antworten oder Kommentaren nicht behandelt wurden.
Die Frage geht implizit davon aus, dass es sich um eine objektorientierte Sprache im Simula-Stil handelt, die selbst einschränkend ist. In den meisten Sprachen, auch wenn es sich um Objekte handelt, ist nicht alles ein Objekt. Die Maschinerie zur Implementierung von Destruktoren würde Kosten verursachen, die nicht jeder Sprachimplementierer zu zahlen bereit ist.
C ++ hat einige implizite Garantien für die Reihenfolge der Zerstörung. Wenn Sie beispielsweise eine baumartige Datenstruktur haben, werden die untergeordneten Elemente vor dem übergeordneten Element zerstört. Dies ist in GC-Sprachen nicht der Fall, sodass hierarchische Ressourcen möglicherweise in einer unvorhersehbaren Reihenfolge freigegeben werden. Dies kann für Nicht-Speicherressourcen von Bedeutung sein.
quelle
Bei der Entwicklung der beiden beliebtesten GC-Frameworks (Java und .NET) erwarteten die Autoren, dass die Finalisierung gut genug funktioniert, um andere Formen der Ressourcenverwaltung zu vermeiden. Viele Aspekte des Sprach- und Framework-Designs können erheblich vereinfacht werden, wenn nicht alle Funktionen für ein 100% zuverlässiges und deterministisches Ressourcenmanagement erforderlich sind. In C ++ müssen folgende Konzepte unterschieden werden:
Zeiger / Referenz, der ein Objekt identifiziert, das ausschließlich dem Inhaber der Referenz gehört und das nicht durch Zeiger / Referenzen identifiziert wird, von denen der Eigentümer nichts weiß.
Zeiger / Referenz, der ein gemeinsam nutzbares Objekt identifiziert, das niemandem exklusiv gehört.
Zeiger / Referenz , die angibt , ein Objekt , das ist ausschließlich im Besitz vom Inhaber der Referenz, sondern , auf dem durch „Ansichten“ hat der Besitzer keine Möglichkeit , Tracking zugänglich sein kann.
Zeiger / Verweis, der ein Objekt identifiziert, das eine Ansicht eines Objekts bereitstellt, dessen Eigentümer eine andere Person ist.
Wenn sich eine GC-Sprache / ein GC-Framework nicht um das Ressourcenmanagement kümmern muss, können alle oben genannten Elemente durch eine einzige Referenz ersetzt werden.
Ich würde die Idee, dass die Finalisierung die Notwendigkeit anderer Formen des Ressourcenmanagements eliminieren würde, für naiv halten, aber ob eine solche Erwartung zu diesem Zeitpunkt angemessen war oder nicht, hat die Geschichte seitdem gezeigt, dass es viele Fälle gibt, in denen ein präziseres Ressourcenmanagement erforderlich ist, als es die Finalisierung vorsieht . Ich bin zufällig der Meinung, dass die Anerkennung von Eigentumsrechten auf der Ebene der Sprache / des Frameworks ausreicht, um die Kosten zu rechtfertigen (die Komplexität muss irgendwo vorhanden sein, und die Verlagerung in die Sprache / das Framework würde den Benutzercode vereinfachen), erkenne jedoch, dass erhebliche Kosten entstehen Das Design profitiert von einer einzigen "Art" von Referenz - etwas, das nur funktioniert, wenn die Sprache / das Framework sich nicht mit Fragen der Ressourcenbereinigung befassen.
quelle
Der Destruktor in C ++ macht eigentlich zwei Dinge zusammen. Es gibt RAM frei und es gibt Ressourcen-IDs frei.
Andere Sprachen trennen diese Bedenken, indem der GC für die Freigabe des Arbeitsspeichers zuständig ist, während eine andere Sprachfunktion die Freigabe der Ressourcen-IDs übernimmt.
Darum geht es bei GCs. Sie tun nur eins, um sicherzustellen, dass Ihnen nicht der Speicher ausgeht. Wenn der RAM unendlich ist, werden alle GCs ausgemustert, da es keinen wirklichen Grund mehr für ihre Existenz gibt.
Sprachen bieten verschiedene Möglichkeiten zum Freigeben von Ressourcen-IDs:
Handbuch
.CloseOrDispose()
über den Code verteiltHandbuch
.CloseOrDispose()
im Handbuch "finally
Block" verstreutHandbuch „Ressourcen - ID - Blöcke“ (dh
using
,with
,try
-mit-Ressourcen , usw.) , die automatisiert.CloseOrDispose()
nachdem der Block wird getangarantiert „Ressource - ID - Blöcke“ , die automatisiert
.CloseOrDispose()
nach dem Block getanViele Sprachen verwenden manuelle (im Gegensatz zu garantierten) Mechanismen, die eine Möglichkeit für ein Missmanagement der Ressourcen schaffen. Nehmen Sie diesen einfachen NodeJS-Code:
..wenn der Programmierer vergessen hat, die geöffnete Datei zu schließen.
Solange das Programm ausgeführt wird, steckt die geöffnete Datei in der Schwebe. Dies ist einfach zu überprüfen, indem Sie versuchen, die Datei mit HxD zu öffnen und zu überprüfen, ob dies nicht möglich ist:
Das Freigeben von Ressourcen-IDs in C ++ - Destruktoren ist ebenfalls nicht garantiert. Man könnte denken , RAII funktioniert wie garantiert „Ressource - ID - Blöcke“, doch im Gegensatz zu „Ressource - ID - Blöcken“, die Sprache C ++ ist noch nicht das Objekt aus dem RAII Block bereitstellt wird geleckt , so dass der RAH - Block nie werden kann getan .
Weil sie Ressourcen-IDs auf andere Weise verwalten, wie oben erwähnt.
Weil sie Ressourcen-IDs auf andere Weise verwalten, wie oben erwähnt.
Weil sie Ressourcen-IDs auf andere Weise verwalten, wie oben erwähnt.
Java hat keine Destruktoren.
In den Java-Dokumenten wird Folgendes erwähnt :
..aber das Einfügen von Code zur Verwaltung von Ressourcen-IDs
Object.finalizer
wird größtenteils als Anti-Pattern angesehen ( vgl. ). Dieser Code sollte stattdessen auf der Anrufseite geschrieben werden.Für Personen, die das Anti-Pattern verwenden, ist ihre Rechtfertigung, dass sie möglicherweise vergessen haben , die Ressourcen-IDs an der Anrufstelle freizugeben. So tun sie es wieder in der Finalizerthread, nur für den Fall.
Es gibt nicht viele Anwendungsfälle für Finalizer, da sie zum Ausführen von Code zwischen dem Zeitpunkt, an dem keine starken Verweise mehr auf das Objekt vorhanden sind, und dem Zeitpunkt, an dem der Speicher des Objekts vom GC zurückgefordert wird, dienen.
Ein möglicher Anwendungsfall ist, wenn Sie ein Protokoll der Zeit zwischen dem Sammeln des Objekts durch den GC und dem Zeitpunkt führen möchten, zu dem keine starken Verweise mehr auf das Objekt als solches vorhanden sind:
quelle
Ich habe in Dr. Dobbs wrt c ++ einen Verweis dazu gefunden, der allgemeinere Ideen hat, wonach Destruktoren in einer Sprache, in der sie implementiert sind, problematisch sind. Eine grobe Idee hier scheint zu sein, dass ein Hauptzweck von Destruktoren darin besteht, die Speicherfreigabe zu handhaben, und das ist schwer richtig zu erreichen. Der Speicher wird stückweise zugewiesen, aber verschiedene Objekte werden verbunden, und dann sind die Verantwortlichkeiten / Grenzen für die Freigabe nicht so klar.
Die Lösung für dieses Problem eines Garbage Collectors wurde vor Jahren entwickelt. Die Garbage Collection basiert jedoch nicht auf Objekten, die beim Verlassen der Methode aus dem Gültigkeitsbereich verschwinden (dies ist eine konzeptionelle Idee, die schwer zu implementieren ist), sondern auf einem Collector, der periodisch, etwas nicht deterministisch ausgeführt wird. wenn die App "Speicherdruck" (dh zu wenig Speicher) erfährt.
mit anderen Worten, das bloße menschliche Konzept eines "neu nicht genutzten Objekts" ist tatsächlich in gewisser Weise eine irreführende Abstraktion in dem Sinne, dass kein Objekt "augenblicklich" nicht genutzt werden kann. Nicht verwendete Objekte können nur "entdeckt" werden, indem ein Garbage Collection-Algorithmus ausgeführt wird, der den Objektreferenzgraphen durchläuft, und die leistungsstärksten Algorithmen werden mit Unterbrechungen ausgeführt.
Es ist möglich, dass ein besserer Garbage Collection-Algorithmus entdeckt werden muss, der unbenutzte Objekte nahezu augenblicklich identifizieren kann, was dann zu einem konsistenten Destruktor-Aufrufcode führen könnte, aber einer wurde nach vielen Jahren der Forschung in diesem Bereich nicht gefunden.
Die Lösung für Ressourcenverwaltungsbereiche wie Dateien oder Verbindungen scheint darin zu bestehen, Objekt- "Manager" zu haben, die versuchen, ihre Verwendung zu handhaben.
quelle