[Rätsel] Garbage Collector / Maximal eine Referenz

Wir haben 3 oder mehr abstrakte Objekte. Auf all diese Objekte zeigt eine static Referenz. Sie können also nicht vom Garbage Collector aufgeräumt werden.

Frage: Kann man ein Konstrukt aus Referenzen zwischen diesen Objekten aufbauen, so das die Objekte nur dann vom Garbage Collector aufgeräumt werden, wenn nur noch auf eines dieser Objekte eine static Referenz zeigt?

Beispiel: Wir haben die Objekte A, B, C. Auf alle zeigt eine static Referenz. Wird die Referenz auf C entfernt (null) soll C nicht aufgeräumt werden. Erst wenn auch die Referenz auf [A oder B] auf null gesetzt wird, soll [A oder B] und C aufgeräumt werden.

Natürlich können alle Möglichkeiten von Java zu Hilfe genommen werden. Inklusive Soft, Weak und Phantomreferences.

Die Frage kam mir vor ein paar Tagen, und seit dem experimentiere ich mit dem Gedanken herum. Vielleicht hat hier jemand mehr Grips als ich.

Hier ist eine (mögliche) Lösung die ich mir ausgedacht habe:

Mögliche Lösung


Die Pfeile sind Referenzen.
Die gestrichelten Pfeile sind Weakreferences.
Die Punkt-Strich-Pfeile funktionieren wie eine normale Referenz aber mit etwas Logik dahinter.

Funktionsweise:

  • Um die Objekte A, B, und C wird eine Wrapperklasse eingeführt [Referenz A/B/C].
  • Immer wenn eine Methode der Wrapperklasse aufgerufen wird, schaut die selbige nach ob die WeakReferences von [BiBinding] auf die anderen [Referenz] Objekte noch existieren.
  • Wenn nur noch die WeakReferenz auf die Wrapperklasse selbst existiert, kündigt sie die Referenz zu [BiBinding] auf und das gesamte Konstrukt wird Garbage Collected

Das funktioniert zwar, aber nur solange die Objekte auch aktiv verwendet werden. Ansonsten bleibt ein Memory-Leak zurück.

Der Punkt ist mir nicht ganz klar. Ich rate mal wild: „noch existieren“ könnte bedeuten, dass die Referenzen noch nicht enqueued wurden. Aber … das dürfte ja so nicht funktionieren. In dem Moment, wo eine weak reference enqueued ist, wurde das Objekt ja schon finalized.

Vielleicht ein Detail: Die Objekte sind, dem Diagramm nach, gleichberechtigt. Das Ziel ist also dass

  • Wird eine Referenz auf A, B, oder C entfernt, passiert nichts
  • Werden die Referenzen auf X und Y entfernt, werden X und Y entfernt (wobei X und Y zwei verschiedene Objekte von A,B,C sind)

(Speziell: Es ist nicht so, dass „C“ ein besonderes Verhältnis zu A/B hat, richtig?)


Zu dem Punkt mit dem „aktiven Verwenden“: Ich habe hier ein paar Exprimente anskizziert und ein bißchen rumprobiert, und hätte diesen Punkt intuitiv versucht, abzuhandeln, indem es eben eine Klasse gibt, die such darum kümmert, mit einem Thread, der entweder irgendwas pollt (meh…), oder (blocking, nicht-polling) auf ein referenceQueue.remove() wartet.

Naja, BiBinding wird eine Liste mit WeakReference haben. Bisschen Peusodcode wie das aussehen könnte

boolean valid() {
   return weakreferences.stream().filter(r -> r.get() != null).count() > 1;
}

Eine ReferenceQueue ist da nicht notwendig. (Bei WeakReferenzen ist finalize nach der Queue, bei Phantom davor.)

Richtig. Die Objekte sind gleichberechtigt. Also alle Objekte mit keiner externen Referenz mehr, sollen vom Garbage Collector aufgeräumt werden können, wenn nur noch ein Objekt eine Referenz hat. (Oder keins, aber das ist ja logisch)

Stimmt. Daran hatte ich nicht gedacht. Ist ein guter Punkt, wenn auch vielleicht etwas Overengineered für mögliche Usecases :smiley: Aber tatsächlich eine Lösung.
Schöner wäre natürlich eine passive Konstruktion. Ob das möglich ist kann ich ernsthaft nicht beurteilen.

Ich hab jetzt 5 verschiedene Ideen skizziert, aber die Schwierigkeit ist liegt darin das alle Objekte gleichberechtigt sind und das in jeder Konfiguration funktionieren soll.

Ich hätte nicht sagen dürfen „In dem Moment, wo eine weak reference enqueued ist, wurde das Objekt ja schon finalized.“, sondern vielleicht eher sowas wie „In dem Moment, wo eine Weak reference bei get() ein null liefert, kann das Objekt ja gefinalized werden“.

Aber der Punkt, auf den ich rauswollte, ist in beiden Fällen der gleiche: Wenn das Ziel darin besteht, zu verhindern, dass ein Objekt GC’d wird, dann ist das if (get()==null) (genau wie die Queue) ja nur eine Möglichkeit, um festzustellen, dass es … zu spät ist, um das noch zu verhindern.

(Irgendwie habe ich gerade das Gefühl, entweder bei der Frage oder beim Verhalten von *References was grundsätzlich falsch zu verstehen. Dazu kommt z.B. dass in der Doku steht " An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable.", aber bei meinen Tests eben finalize() auf einem Objekt aufgerufen wurde, auf das noch eine Phantom Reference existiert, bei dem ich aber nicht clear aufgerufen habe - sollte das so sein? …)

Was man bräuchte wäre halt ein ganz pragmatisch-altmodisches reference-counting. Aber in Java ist das nicht so einfach. (Websuche liefert eine lib, die sowas macht, aber hier in dieser Form wohl nicht hilfreich…). Und… ich gehe davon aus, dass das wirklich ein Rätsel sein soll. Wenn das Ziel ein echter Anwendungsfall wäre, würde man sich halt eine andere Lösung überlegen.

Schöner wäre natürlich eine passive Konstruktion. Ob das möglich ist kann ich ernsthaft nicht beurteilen.

Eine Schwierigkeit besteht darin, dass man sich in das, was die VM macht, in diesem Fall nicht einklinken kann. Einige Aktionen passieren einfach auf nativer Seite, so dass man auch mit fiesen Tricksereien (überschreiben von Methoden, Unsafe, Reflection) da nicht rankommt…