Как сборщик мусора здесь избегает бесконечного цикла?

101

Рассмотрим следующую программу C #, я отправил ее на codegolf в качестве ответа на создание цикла без цикла:

class P{
    static int x=0;
    ~P(){
        System.Console.WriteLine(++x);
        new P();
    }
    static void Main(){
        new P();
    }
}

На мой взгляд, эта программа выглядит как бесконечный цикл, но кажется, что она выполняется в течение нескольких тысяч итераций, а затем программа успешно завершается без ошибок (ошибок не возникает). Это нарушение спецификации, из-за которого финализатор в Pконечном итоге не вызывается?

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

Исходный код поста для гольфа :: /codegolf/33196/loop-without-looping/33218#33218

Майкл Б.
источник
49
Боюсь запускать это.
Эрик Шеррер
6
То, что финализатор не вызывается, безусловно, находится в сфере допустимого поведения . Я не знаю, почему нужно запускать несколько тысяч итераций, я бы ожидал нулевых вызовов.
27
CLR имеет защиту от потока финализатора, который никогда не сможет завершить свою работу. Он принудительно завершает его через 2 секунды.
Hans Passant
2
Итак, настоящий ответ на ваш вопрос в заголовке заключается в том, что он позволяет избежать этого, просто позволяя бесконечному циклу работать в течение 40 секунд, а затем он завершается.
Лассе В. Карлсен
4
От попытки кажется, что программа просто убивает все через 2 секунды, несмотря ни на что. На самом деле, если вы продолжите создавать потоки, это продлится немного дольше :)
Майкл Б.

Ответы:

110

Согласно Рихтеру во втором издании CLR через C # (да, мне нужно обновить):

Стр. Решебника 478

Для (CLR закрывается) каждому методу Finalize дается примерно две секунды для возврата. Если метод Finalize не возвращается в течение двух секунд, CLR просто прекращает процесс - больше не вызываются методы Finalize . Кроме того, если для вызова методов Finalize всех объектов требуется более 40 секунд , среда CLR просто завершает процесс.

Кроме того, как упоминает Серви, у него есть собственная ветка.

Эрик Шеррер
источник
5
Каждый метод finalize в этом коде занимает менее 40 секунд на объект. То, что новый объект создается и затем имеет право на финализацию, не имеет отношения к текущему финализатору.
Джейкоб Кролл
2
На самом деле это не то, что делает работу. Также существует тайм-аут на освобождение свободной очереди при завершении работы. Вот почему этот код не работает: он продолжает добавлять новые объекты в эту очередь.
Hans Passant
Просто подумав об этом, вы не очистите свободную очередь так же, как «Кроме того, если для вызова методов Finalize всех объектов требуется более 40 секунд, CLR просто убивает процесс».
Eric Scherrer
23

Финализатор не работает в основном потоке. У финализатора есть собственный поток, который запускает код, и это не поток переднего плана, который будет поддерживать работу приложения. Основной поток эффективно завершается сразу же, и в этот момент поток финализатора просто запускается столько раз, сколько у него есть возможность, прежде чем процесс будет остановлен. Ничто не поддерживает работу программы.

Сервировка
источник
Если финализатор не завершил работу через 40 секунд после того, как программа должна была выйти из-за отсутствия активного основного потока, он будет завершен, и процесс завершится. Это старые значения, поэтому Microsoft, возможно, уже изменила фактические числа или даже весь алгоритм. Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse V. Karlsen
@ LasseV.Karlsen Это задокументированное поведение языка или просто то, как MS решила реализовать свои финализаторы в качестве детали реализации? Я ожидал последнего.
Servy 09
Я тоже жду последнего. Самая официальная ссылка на это поведение, которое я видел, - это то, что Эрик опубликовал в своем ответе из книги Джеффри Рихтера CLR через C #.
Лассе В. Карлсен
8

Сборщик мусора - это не активная система. Он запускается «иногда» и в основном по запросу (например, когда все страницы, предлагаемые ОС, заполнены).

Большинство сборщиков мусора работают в подпотоках в стиле первого поколения. В большинстве случаев переработка объекта может занять несколько часов.

Единственная проблема возникает, когда вы хотите прекратить работу программы. Однако это не проблема. При использовании killОС будет вежливо попросить прекратить процессы. Однако, когда процесс остается активным, можно использовать место, kill -9где операционная система удаляет весь контроль.

Когда я запустил ваш код в интерактивной csharpсреде, я получил:

csharp>  

1
2

Unhandled Exception:
System.NotSupportedException: Stream does not support writing
  at System.IO.FileStream.Write (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushBytes () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushCore () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] val) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.String val) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.Write (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.SynchronizedWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.Console.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at P.Finalize () [0x00000] in <filename unknown>:0

Таким образом, ваша программа вылетает из-за того, что stdoutона заблокирована терминологией среды.

При удалении Console.WriteLineи убийстве программы. Через пять секунд программа завершится (другими словами, сборщик мусора откажется и просто освободит всю память без учета финализаторов).

Виллем Ван Онсем
источник
Удивительно, что интерактивный csharp взрывается по совершенно другим причинам. В исходном фрагменте программы не было строки записи консоли, мне любопытно, если бы она тоже завершилась.
Michael B
@MichaelB: Я тоже это тестировал (см. Комментарий ниже). Он ждет пять секунд, а затем завершается. Я предполагаю, что финализатор первого Pэкземпляра просто истек.
Виллем Ван Онсем 09