Переменная цикла не собирается - PullRequest
9 голосов
/ 24 мая 2011

У меня есть переменная цикла, которая, похоже, не собирает мусор (согласно профайлеру памяти Red-Gate ANTS), несмотря на то, что она вышла из области видимости.

Код выглядит примерно так:

while (true)
{
    var item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue
    // do something with item
}

Насколько я могу судить, ссылка на item сохраняется до тех пор, пока не вернется blockingQueue.dequeue().Является ли это предполагаемым поведением, или это может быть ошибка в профилировщике памяти?

Во-вторых, если это намеренное поведение, как я заставлю item собираться в конце тела цикла?Установка null не приводит к его сбору.Это важно, поскольку очередь может потенциально блокироваться в течение длительного времени, и item ссылается на довольно большое дерево объектов.

Обратите внимание, что в документации к профилировщику говорится, что GC выполняется перед созданием снимка памяти, иссылка отсутствует в очереди финализатора.

Мне удалось воспроизвести ту же проблему с кодом здесь .

Обновление

Код в сущности был слегка ошибочным в том смысле, что он на законных основаниях сохранил ссылку в GetFoo().После его изменения объект теперь получает , когда явно установлен на null.Тем не менее, я полагаю, что ответ Ганса объясняет ситуацию, с которой я сталкиваюсь в моем реальном коде.

Ответы [ 6 ]

7 голосов
/ 24 мая 2011

Вероятным источником этой проблемы является оптимизатор джиттера. Вот пример:

class Program {
    static void Main(string[] args) {
        while (true) {
            var input = Console.ReadLine();
            Console.WriteLine(input);
            input = null;
        }
    }
}

Создает этот машинный код:

            while (true) {
                var input = Console.ReadLine();
00000000  push        ebp                    ; setup stack
00000001  mov         ebp,esp 
00000003  push        esi  
00000004  call        6E0208F0               ; Console.In property getter
00000009  mov         ecx,eax 
0000000b  mov         eax,dword ptr [ecx] 
0000000d  call        dword ptr [eax+64h]    ; TextReader.ReadLine()
00000010  mov         esi,eax                ; assign input variable
                Console.WriteLine(input);
00000012  call        6DB7BE38               ; Console.Out property getter
00000017  mov         ecx,eax
00000019  mov         edx,esi
0000001b  mov         eax,dword ptr [ecx] 
0000001d  call        dword ptr [eax+000000D8h] ; TextWriter.WriteLine()
00000023  jmp         00000004               ; repeat, note the missing null assigment

В регистре esi хранится переменная input . Обратите внимание, что он никогда не возвращается к нулю, он всегда сохраняет ссылку на последнюю введенную строку. Оптимизатор удалил пустой оператор присваивания. Сборщик мусора получает подсказки времени жизни от дрожания, он будет говорить, что ссылка жива в течение всего цикла.

Проблема возникает на втором и последующем проходе, когда вы никогда не набираете что-либо, тогда ReadLine () блокируется (аналогично вашей очереди блокировки), а значение регистра esi продолжает ссылаться на строку. Он никогда не будет собираться мусором в течение цикла, по крайней мере, пока он не будет переназначен.

Нет чистого решения для этого. Вот ужасный:

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void NullReference<T>(ref T obj) where T : class {
        obj = null;
    }

и использование:

        while (true) {
            var input = Console.ReadLine();
            Console.WriteLine(input);
            NullReference(ref input);
        }
1 голос
/ 24 мая 2011

До вызова Dequeue значение элемента не было перезаписано и все еще используется правильно?Лучшее, что вы можете сделать, это установить его в null, вызов GC.Collect (), но вы не гарантированно получите эту переменную и не сможете заставить ее быть собранной, так зачем беспокоиться?

0 голосов
/ 24 мая 2011

Ну, следующие два фрагмента кода дают один и тот же код:

int i = 0;
System.Object x;
while(i < 100){
    x = new System.Object();
    System.Console.WriteLine(x.ToString());
    i++;
}

Теперь пытаемся зависеть от лексической области видимости для освобождения локальной ссылки в x:

int i = 0;
while(i < 100){
    System.Object x = new System.Object();
    System.Console.WriteLine(x.ToString());
    i++;
}

Результат одинаков в обоих случаях. Локальное удержание ссылки с именем x не обнуляется, когда итерация цикла заканчивается. Даже если нам не удастся перейти к началу цикла, для local never установлено значение null. Вместо этого компилятор повторно использует этот слот локальной переменной при возникновении такой возможности.

Если вы явно установите x в null, компилятор выдаст il для установки локального значения в null, даже если у вас включены флаги оптимизации. Если это оптимизировано, это происходит в JIT, а не в статическом компиляторе.

0 голосов
/ 24 мая 2011

Я думаю, что проблема в том, что item никогда не выходит из области видимости, пока цикл не закончится.ГХ не достаточно умен, чтобы признать, что значение в item не будет использоваться до того, как будет перезаписано, поэтому он не сможет его собрать.сделанное с ним удалит последнюю ссылку и позволит вашему объекту быть собранным.

0 голосов
/ 24 мая 2011

Если вы закончили с элементом, вы можете освободить ссылку на него в конце тела цикла:

item = null;

Что касается сбора мусора, независимо от того, насколько большой элементесли других ссылок на него нет и сборщик мусора не собрал его, сборщик мусора не считает, что его нужно собирать.

Пусть сборщик мусора выполнит свою работу.Он будет собирать вещи своевременно и будет делать это эффективно, торгуя как памятью, так и временем.

0 голосов
/ 24 мая 2011
while (true)
{
    {
        var item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue
        // do something with item
    }
    // do others that might be blocking for a long time
}

Я подозреваю, что включение в блок может работать. Если это одноразовый, вы могли бы

while (true)
{
    using (var item = blockingQueue.dequeue(); 
    {
        // do something with item
    }
    // do others that might be blocking for a long time
}

Возможно, я вас неправильно понял, но есть еще одна возможность справиться с другой ситуацией:

while (true)
{
    var item = null;
    item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue
    // do something with item
    item = null;
}
...