Получают ли аргументы конструктора GC'ed? - PullRequest
5 голосов
/ 27 декабря 2011

Я использую систему, которая должна инициализировать многие объекты с использованием транзакций, и по причинам, выходящим за рамки этого вопроса, эти транзакции должны быть переданы в конструкторы. Как это:

trait Mutable

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

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

Теперь я боюсь, что существует проблема с сборкой мусора транзакций:

val u = new User(new Txn(1234))
System.gc()  // hmmm, nothing seems to happen?

Итак, мой вопрос: получает ли когда-нибудь аргумент конструктора t0 сборщик мусора, или я создаю здесь утечку памяти? В эквивалентном Java-коде, я думаю, у меня будет что-то вроде этого:

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

и я уверен, что t0 собрано. Но так ли это в случае с Scala?

Если нет, как я могу убедиться, что t0 собирает мусор? Помните, что я должен передать транзакцию в качестве аргумента конструктора, поскольку класс User реализует некоторые черты, которые должны быть переданы в методы Txn, поэтому эти методы (например, newID) не могут быть вызванным перед построением User.

Ранее я пытался сконструировать все, что использует транзакцию, за пределами пользовательского объекта с тоннами lazy взаимозависимых значений, но это было действительно грязно. Например, это, которое уже наполовину не читается, вызывает переполнение стека:

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 } // oops, should be lazy val id!
  u
}

val u = newUser(new Txn(1234))

Вы можете себе представить, что действительно ужасно, что компилятор не обнаружит проблему с отсутствующим отложенным значением val, поэтому я определенно предпочел бы вариант arg конструктора.

Ответы [ 3 ]

6 голосов
/ 27 декабря 2011

Аргументы конструктора получают GCed , если они не используются за пределами статического инициализатора .Вы можете проверить байт-код и убедиться, что в этом случае не сохраняется ссылка на аргумент конструктора.

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."<init>":()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

Обратите внимание, что аргумент загружен (строка 5) и для него вызывается метод (строка 6), но сохраняется только ответ (строка 11) до выхода из конструктора (строка 14).

5 голосов
/ 27 декабря 2011

Если абсолютно необходимо, я рекомендую использовать javap, чтобы увидеть, во что скомпилирован класс.Некоторые правила, чтобы избежать превращения аргумента конструктора в параметр класса:

  • Не используйте его на def или lazy val.
  • Не используйте его при присваиваниикоторые соответствуют шаблонам (например, val (a, b) = f(x)).
  • И, конечно, не объявляйте его как val или var.
0 голосов
/ 27 декабря 2011

Мне показалось, что проблема была в конкретной ситуации, когда System.gc() не мог бы сразу сработать.

Но я могу наблюдать, как финализация происходит в простом REPL, а также в некотором скомпилированном коде, поэтому я предполагаю, что ответ «да, аргумент конструктора собирает мусор».

Это также работает при прохождении через суперклассы, что также является хорошей новостью:

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

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