6 Stimmen

Werden Konstruktorargumente von der Müllsammlung (GC) bereinigt?

Ich verwende ein System, das viele Objekte unter Verwendung von Transaktionen initialisieren muss, und aus Gründen, die über den Rahmen dieser Frage hinausgehen, müssen diese Transaktionen in die Konstruktoren übergeben werden. So:

trait Mutable

class Txn(i: Int) {
  def newID(implicit m: Mutable): Int = i
  override def finalize(): Unit = println("Finalisiert " + i)
}

class User(t0: Txn) extends Mutable {
  val id = t0.newID(this)
}

Jetzt fürchte ich, dass es ein Problem mit dem Garbage Collector der Transaktionen gibt:

val u = new User(new Txn(1234))
System.gc()  // hmmm, es scheint nichts zu passieren?

Also lautet meine Frage: Wird das t0 Konstruktorargument jemals vom Garbage Collector eingesammelt, oder erschaffe ich hier ein Memory-Leak? In einem äquivalenten Java-Code hätte ich wohl so etwas:

public class User implements Mutable {
    final int id;
    public User(Txn t0) {
        id = t0.newID(this);
    }
}

und ich bin sicher, dass t0 eingesammelt wird. Aber ist das im Fall von Scala wahr?

Wenn nicht, wie kann ich sicherstellen, dass t0 eingesammelt wird? Denken Sie daran, dass ich die Transaktion als Konstruktorargument übergeben muss, weil die User-Klasse einige Traits implementiert, die in die Methoden von Txn übergeben werden müssen, sodass diese Methoden (wie newID) nicht aufgerufen werden können, bevor User konstruiert wird.

Ich habe bereits versucht, alles außerhalb des Benutzerobjekts zu konstruieren, mit tonnenweise verzögerten, voneinander abhängigen Werten, aber das war wirklich unordentlich. Beispielsweise führt dies, was bereits halbwegs unleserlich ist, zu einem Stackoverflow:

trait User extends Mutable { def id: Int }

def newUser(implicit tx: Txn): User = {
  lazy val _id: Int = tx.newID(u)
  lazy val u  = new User { val id: Int = _id } // ups, sollte lazy val id sein!
  u
}

val u = newUser(new Txn(1234))

Man kann sich vorstellen, wie ärgerlich es ist, dass der Compiler das Problem mit dem fehlenden lazy val hier nicht erkennt, daher würde ich definitiv die Konstruktorargumentvariante bevorzugen.

6voto

Rex Kerr Punkte 164629

Konstruktorargumente werden verworfen wenn sie nicht über den statischen Initialisierer hinaus verwendet werden. Sie können den Bytecode überprüfen und feststellen, dass in diesem Fall keine Referenz auf das Konstruktorargument erhalten bleibt.

class WillNotStore(s: Seq[Int]) { val length = s.length }

public WillNotStore(scala.collection.Seq);
  Code:
   0:   aload_0
   1:   invokespecial   #18; //Method java/lang/Object."":()V
   4:   aload_0
   5:   aload_1
   6:   invokeinterface #22,  1; //InterfaceMethod scala/collection/SeqLike.length:()I
   11:  putfield    #11; //Field length:I
   14:  return

Beachten Sie, dass das Argument geladen wird (Zeile 5) und eine Methode darauf aufgerufen wird (Zeile 6), aber nur die Antwort gespeichert wird (Zeile 11), bevor der Konstruktor endet (Zeile 14).

0 Stimmen

Hah! Ich habe ihm gerade gesagt, javap zu verwenden, und hier benutzt du es, um seine Frage zu beantworten. :-)

0 Stimmen

@DanielC.Sobral - scala -cp /path/to/tools.jar + :javap -c WillNotStore dauert nur wenige Sekunden....

0 Stimmen

@Sciss - Daniels Antwort ist umfassender; das Muster passt auch im Initialisierer ist ein seltsamer Sonderfall, den ich vergessen hatte. DelayedInit ist eine andere (obwohl sie dort offensichtlich notwendig ist, da sie konzeptionell wie lazy val funktioniert).

5voto

Daniel C. Sobral Punkte 290004

Wenn unbedingt erforderlich, empfehle ich Ihnen, javap zu verwenden, um zu sehen, in was die Klasse kompiliert wurde. Einige Regeln, um zu vermeiden, dass das Konstruktorargument in einen Klassenparameter umgewandelt wird:

  • Verwenden Sie es nicht bei def oder lazy val.
  • Verwenden Sie es nicht bei Zuweisungen, die Musterabgleiche durchführen (wie val (a, b) = f(x)).
  • Und natürlich deklarieren Sie es nicht als val oder var.

1 Stimmen

Die Überprüfung ist vielleicht der beste Rat! Der einfachste Weg zu überprüfen besteht normalerweise darin, den -private -Flag in javap zu verwenden und sich keine Sorgen über den Bytecode zu machen. Dort werden die privaten Felder zuerst aufgelistet (unter Verwendung der Java-Syntax), zu denen etwas wie x$1 für Konstruktorargumente gehören wird.

0voto

0__ Punkte 65479

Das Problem, das ich hatte, schien mit einer bestimmten Situation zu sein, in der System.gc() nicht sofort eingreifen würde.

Aber ich kann beobachten, wie die Finalisierung in einem einfachen REPL und auch in einigen kompilierten Codes stattfindet, also denke ich, lautet die Antwort: 'Ja, das Konstruktorargument wird tatsächlich freigegeben'.

Es funktioniert auch beim Durchlaufen von Oberklassen, was weitere gute Nachrichten sind:

abstract class Underlying(t0: Txn) extends Mutable {
  val id1 = t0.newID(this)
}

class User(t0: Txn) extends Underlying(t0) {
   val id2 = t0.newID(this)
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X