Как .NET CLR различает управляемый из неуправляемых указателей?

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

Теперь возникает вопрос: как сборщик мусора .NET выясняет, является ли указатель на объект внутри кучи GC фактически управляемым указателем или случайным целым, которое имеет значение, соответствующее действительному адресу?

Очевидно, если он не может отличить два, тогда могут быть утечки памяти, поэтому мне интересно, как это работает. Или, может быть, я говорю это - у .NET есть потенциал для утечки памяти?: О

Ответ 1

Как указывали другие, GC точно знает, какие поля каждого блока в стеке и куче управляются ссылками, потому что GC и дрожание знают тип всего.

Однако, ваша точка зрения хорошо взламывается. Представьте себе полностью гипотетический мир, в котором происходит два вида управления памятью в одном и том же процессе. Например, предположим, что у вас есть полностью гипотетическая программа под названием "InterMothra Chro-Nagava-Sploranator", написанная на С++, которая использует традиционное управление памятью с подсчетом COM-стиля, где все просто указатель на обработку памяти, а объекты освобождаются путем вызова Выпустите метод правильного количества раз. Предположим, что Sploranator гипотетически имеет язык сценариев JabbaScript, который поддерживает собранный мусором пул объектов.

Проблема возникает, когда объект JabbaScript имеет ссылку на объект без управления Sploranator, и тот же объект Sploranator имеет ссылку назад. Это круговая ссылка, которая не может быть нарушена сборщиком мусора JabbaScript, поскольку она не знает о макете памяти объекта Sploranator. Таким образом, существует потенциал для утечек памяти.

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

Другой способ - использовать эвристику; GC может посвятить поток процессора для сканирования всей памяти, ищущей целые числа, которые являются указателями на его объекты. Это звучит очень много, но он может опускать незавершенные страницы, страницы в собственной управляемой куче, страницы, которые, как известно, содержат только код и т.д. GC может предположить, что если он считает, что объект может быть мертв, и он не может найти никакого указателя на этот объект в любой памяти вне своего контроля, тогда объект почти наверняка будет мертв.

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

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

Схожесть между стратегией сбора мусора, описанной здесь, и фактическая стратегия GC любого продукта почти полностью совпадают. Эрик размышляет о деталях реализации сборщиков мусора гипотетических несуществующих продуктов только для развлекательных целей.

Ответ 2

Сборщику мусора не нужно выводить, является ли конкретный байтовый шаблон (4 или 8 байтов) указателем или нет - он уже знает.

В CLR все строго типизировано, поэтому сборщик мусора знает, являются ли байты int, a long, ссылкой на объект, нетипизированным указателем и т.д. и т.д.

Макет объекта в памяти определяется в классе компиляции - метаданные, хранящиеся в сборке, дают тип и расположение каждого члена экземпляра.

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

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

Блог Eric Lippert - это хорошее место, чтобы узнать больше - Ссылки не являются адресами, это место для начала.

Ответ 3

Помните, что вся управляемая память управляется CLR. Любая фактическая управляемая ссылка была создана CLR. Он знает, что он создал, а что нет.

Если вы действительно чувствуете, что должны знать подробности реализации, тогда вы должны прочитать CLR через С# Джеффри Рихтер. Ответ не прост - он цитирует немного больше, чем можно ответить на SO.

Ответ 4

Хорошо, когда JIT-код компилятора знает, в каких местах он помещает ссылку на объекты. Всякий раз, когда вы используете поле в методе, который содержит ссылку, он знает, что в этом месте есть ссылка. Эта информация также может быть сохранена при использовании JIT кода.

Теперь ссылка указывает на объект. Каждый объект имеет указатель на свой класс (метод .GetType()). В принципе, GC теперь может взять указатель, следовать ему, прочитать тип объекта. Тип говорит вам, есть ли другие поля, содержащие ссылки на другие объекты. Таким образом, GC может пройти весь стек и кучу.

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

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

Ответ 5

Посмотрите Сбор мусора: автоматическое управление памятью в Microsoft.NET Framework
(Некоторые технические детали могут быть немного устаревшими, но описанная структура действительна.)

Некоторые краткие моменты из статьи....

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

...

Каждое приложение имеет набор корней. Корни идентифицируют хранилище местоположения, которые относятся к объектам на управляемая куча или объекты, которые имеют значение null. Например, все глобальные и статические указатели объектов в приложение считается частью корни приложения. К тому же, любой локальный объект переменной/параметра указатели на стеке потоков рассматриваемая часть заявки корнеплоды. Наконец, любые регистры процессора содержащие указатели на объекты в управляемая куча также считается частью корней приложения. Список активных корней поддерживается компилятор "точно в срок" (JIT) и общий языковой среды исполнения и доступный сборщику мусора алгоритм.

...

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

По вопросу...

... или случайное целое число, которое происходит с имеют значение, соответствующее действительный адрес?.... утечки памяти?

Если объект недоступен, GC уничтожит его независимо.

Ответ 6

Когда вы создаете новый объект типа ссылки в .NET, вы автоматически "регистрируете" его с помощью CLR и GC. В этот процесс нельзя вводить случайные типы значений. Другими словами:

CLR не поддерживает некоторую большую, неорганизованную кучу указателей, смешанную с типами значений. Он просто отслеживает объекты, созданные CLR (для целей сбора мусора в любом случае). Любой тип значения будет кратковременным в стеке или быть членом экземпляра класса. Невозможно сбить с толку GC.

Ответ 7

В соответствии с книгой "CLR via С#" среда выполнения точно знает, где найти ссылки/указатели, проверив "внутреннюю таблицу метода". То, что эта внутренняя таблица содержит в реализации microsoft, неизвестно, но может свободно идентифицировать кадры вызова в стеке, локальные переменные и даже какое значение регистры сохраняются для каждого адреса EIP.

В монорежиме используется консервативное сканирование, что означает, что каждое значение в стеке обрабатывается как потенциальный указатель. Это не только переводит на утечки памяти, но также (так как не может обновлять эти значения) объекты, идентифицированные этим, обрабатываются как закрепленные (неотъемлемые от уплотнителя GC), что приводит к фрагментации памяти.

Теперь у моно есть опция "Точная маркировка стека", которая использует GCMaps. Здесь вы можете прочитать подробнее http://www.mono-project.com/Generational_GC#Precise_Stack_Marking

Обратите внимание, что эта реализация не точна, так как она является MS, поскольку она продолжает относиться к текущему кадру консервативно.

Ответ 8

Ссылки имеют заголовки, поэтому это не просто случайное целое число.