Pull to refresh

Comments 13

Например, если программа запускается первый раз и объекты не удалялись, то вместо метода финализатора будет метод-заглушка (вызывающий компиляцию основного тела метода).

А вот есть такая штука как наследование от CriticalFinalizerObject… и в целом это всё уже было (перепечатка отсюда?)…
Да, про CriticalFinalizerObject забыл, сейчас добавлю.

Нет, это не перепечатка, основная информация из Clr via C# и Pro .Net perfomance. Последняя мне очень понравилась, в еще намного легче читать, чем Рихтера. Хотя, конечно, классику знать надо.
> большинство классов стандартной библиотеки на всякий случай имеют финализатор (защита «от дурака») который имеет такой вид:

В итоге финализатор-то вы и не показали :)
Прочитал статью более 5 раз, но до сих пор осталось несколько вопросов:

Объект может быть удален до того, как отработает метод, который этот объект вызывает


Я правильно понимаю, что в данном случае нет ничего необычного — глядя в таблицу методов (с рутами), GC видит, что в дальнейшем на переменную никто не ссылается и просто принимает решение о том, что её можно удалить?

Это работает потому, что методы экземпляров ничем не отличаются от статических методов, кроме того, что в нем передается скрытый параметр — ссылка this, которая ничем не лучше остальных параметров метода. Ну и еще небольшие отличия есть с точки зрения CIL (методы экземпляров всегда вызываются с помощью callvirt, даже те, которые не помечены как виртуальные, когда как для статических методов используется простой call)


Не могли бы вы поподробнее описать, что здесь имелось в виду и как на самом деле на нижнем уровне происходит вызов (через call или callvirt, по какому адресу осуществляется вызов, какое значение имеет указатель this и как влияет на вызов удаление экземпляра сборщиком мусора).
Немного промахнулся, см. ниже )
Я правильно понимаю, что в данном случае нет ничего необычного — глядя в таблицу методов (с рутами), GC видит, что в дальнейшем на переменную никто не ссылается и просто принимает решение о том, что её можно удалить?

Никакой таблицы методов, насколько я помню механизм, у каждого объекта есть свойство, которое показывает строчку (вернее, адрес команды), после которой он может быть безопасно удален. Поэтому например метод GC.KeepAlive выглядит так:
[MethodImpl(MethodImplOptions.NoInlining), ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), __DynamicallyInvokable]
public static void KeepAlive(object obj)
{
}

Он ничего не делает, просто перезаписывает адрес безопасного удаления. Единственный нюанс: атрибут NoInlining, иначе компилятор бы его заинлайнил, ну и весь смысл от него тогда пропал бы.

А в остальном да, все верно.

Не могли бы вы поподробнее описать, что здесь имелось в виду и как на самом деле на нижнем уровне происходит вызов (через call или callvirt, по какому адресу осуществляется вызов, какое значение имеет указатель this и как влияет на вызов удаление экземпляра сборщиком мусора).

когда вы пишете
void Foo(int a,int b)
{
   c = a+b;
}

компилятор превращает это в такой код:
static void Foo(object this, int a,int b)
{
   this.c = a+b;
}

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

что касается call и callvirt можно почитать об этом, например, здесь.
Джефри Рихтер, CLR via C# > Алгоритм сборки мусора

Когда я говорил про таблицу методов, я имел ввиду следующее:

Генерация машинного кода JIТ-компилятором сопровождается созданием внутренней таблицы. Логически каждая ее строка указывает диапазон смещений байтов машинных кодов процессора для этого метода, а также для каждого диапазона — набор адресов памяти и регистры процессора, содержащие корни.


По теме: download.microsoft.com/download/e/2/1/e216b4ce-1417-41af-863d-ec15f2d31b59/DEV490.ppt (Презентация .NET Framework:CLR – Under The Hood, Jeffrey Richter)

30 слайд:

When a method is JIT compiled, the JIT compiler creates a table indicating the method’s roots
The GC uses this table
The table looks something like this…

Start Offset End Offset Roots________________
0x00000000 0x00000020 this, arg1, arg2, ECX, EDX
0x00000021 0x00000122 this, arg2, fs, EBX
0x00000123 0x00000145 fs



Джефри Рихтер, CLR via C# > Мониторинг и контроль времени жизни объектов

Для каждого домена приложения CLR поддерживает таблицу GС-дескрипторов ( GC handle table), с помощью которой приложение отслеживает время жизни объекта или позволяет управлять им вручную. В момент создания домена приложения таблица пуста. Каждый элемент таблицы состоит из указателя на объект в управляемой куче и флага, задающего способ мониторинга или контроля объекта.


А вы говорите видимо как раз про эту таблицу GC-дескрипторов (GC handle table), с помощью которой можно управлять объектами им вручную (как в вашем примере, проставить с помощью GC.KeepAlive флаг о запрете на перемещение, в случае сценария взаимодействия с неуправляемым кодом).
Ну да, все верно (хотя еще бы я был не согласен с Рихтером :) )
Какие-то советы устаревшие… Любой неуправляемый ресурс должен быть «завернут» в SafeHandle или в CriticalHandle. Это избавит от проблем с выгрузкой AppDomain во время инициализации неуправляемого ресурса.

А классы, которые используют неуправляемые ресурсы через безопасные обертки, не нуждаются в финализаторах — ведь финализаторы уже есть в этих обертках.
Да, дал маху, если не возражаете, добавлю ваш совет.

методы экземпляров всегда вызываются с помощью callvirt

Не всегда. Как раз в вашем примере будет call.

Вот пример

Век живи — век учись. Спасибо, интересно)


Для чистоты эксперимента стоит проверить на C# 5.0 который был актуален когда это писалось. Погуглив немного нашел что некоторые оптимизации call/callvirt были добавлены только в 6.0, не факт что на момент написания этой статьи оно было.

Sign up to leave a comment.

Articles