Scala: ленивые валы, вызов по имени, замыкания и утечки памяти

У меня есть процедура scala, создающая большую структуру данных с использованием еще большего индекса в процессе. Поскольку я хочу сделать это за один проход и не запутаться в сложном разрешении приоритета, я использую ленивые валы в результате, инициализированном выражениями, которые могут не иметь правильного значения (или вообще никакого) в момент создания компонента, но сделает это после завершения всего процесса сборки. Это означает, что каждый компонент конечного результата имеет синтетическую ссылку на замыкание со всем моим индексом, и потенциально, пока какой-либо из них все еще находится в памяти, мой индекс не может быть собран мусором. Очевидно, я этого не хочу - в идеале я хотел бы иметь возможность сделать второй проход по структуре, чтобы инициализировать значения, если это необходимо (и гарантировать, что любые ошибки будут обнаружены на этом этапе), и пусть индекс будет собранный мусор. В настоящее время я передаю выражение инициализации по имени через несколько функций и использую его в ленивом объявлении val, что эквивалентно этому:

class Component(init : =>Component) {
   lazy val property = init
}
...
new Component(index.get(parameters))

Это звук? Будет ли синтетическое поле инициализации разыменовано после доступа к lazy val? Что, если я хочу использовать его в функции инициализации, например:

class Component(init : =>Component) {
   private def evaluate = init
   lazy val property = evaluate
}

Существуют ли какие-либо общие правила, которые следует учитывать при программировании с замыканиями?


person Turin    schedule 26.05.2015    source источник
comment
Не могли бы вы немного усложнить реальный код? если index.get уже является функцией, которая возвращает Component, для чего нужна упаковка? Кроме того, какие типы являются изменяемыми (некоторые из них должны быть изменены, иначе нет проблем с порядком инициализации)   -  person Martijn    schedule 26.05.2015
comment
Реальный код довольно огромен, но основной вопрос очень прост: собирается ли синтетический мусор закрытия после ленивой инициализации val, если он используется только в этом инициализаторе (частный случай), и существуют ли какие-либо правила, регулирующие его (общий). В моем случае единственной изменяемой структурой является индекс, используемый в процессе построения, все остальные прямые/циклические ссылки разрешаются с помощью ленивых значений, инициализированных выражениями, возвращающими правильные значения из индекса. Закрытие существует, потому что инициализирующее выражение может быть сложным и иметь аргументы, о которых не должны заботиться компоненты.   -  person Turin    schedule 27.05.2015
comment
Это не обязательно должен быть весь реальный код или даже какой-то реальный код, достаточно, чтобы это был автономный пример, который компилируется и показывает проблемное поведение.   -  person Martijn    schedule 27.05.2015


Ответы (1)


Основная проблема, которую вы описываете, — то, что индекс не может быть собран мусором, — решается путем помещения индекса в изменяемое поле, которое вы очищаете (обнуляете) после создания объекта.

Однако, если вы не знаете, когда был создан ваш объект, и вам нужно, чтобы программа сообщила вам об этом (например, зная, что все ленивые val были заполнены), вам не повезло. Если не ковыряться в памяти с sun.misc.Unsafe, вы не должны знать такие детали. (В этом суть ленивых вальсов.)

Вы можете придумать схему подсчета ссылок, которая немного поможет вам определить себя, когда вы можете очистить поле: увеличить счетчик в поле, когда вы входите в конструктор, сохранить в частном поле количество ленивых значений, которые у вас есть, и уменьшайте этот счетчик (атомарно!) каждый раз, когда вы инициализируете отложенное значение, и если вы нажмете ноль, уменьшите счетчик в поле и обнулите поле, если счетчик ящика достиг нуля.

person Rex Kerr    schedule 26.05.2015
comment
Если бы мне нужно было дойти до такой степени, чтобы принудительно разыменовать индекс, я мог бы просто использовать одну переменную для окончательного «ленивого» значения, а другую переменную для хранения явной функции инициализации, которая будет установлена ​​​​на ноль после ее оценки, но это слишком это шаблонный код, и я надеялся, что компилятор выдает именно такой код в этом примере. - person Turin; 27.05.2015