Страница: 1 | 2 | 3 |
|
Вопрос: GC финализирует объект без причины
|
Добавлено: 01.09.12 20:36
|
|
Автор вопроса: Programmer
|
Есть объект, который наследуется от MemoryStream. Внутри него создается BinaryWriter, который использует этот самый объект. Ссылка на BinaryWriter используется в коде. Т.е. наследник MemoryStream доступен из Call stack -> локальная переменная типа BinaryWriter -> свойство BaseStream.
Во время очередного обращения к BinaryWriter.Write я узнаю, что мой объект был финализирован (в деструкторе устанавливаю флаг). Как такое вообще возможно?
Такое происходит при каждом запуске программы в течении 5-10 минут после запуска.
Ответить
|
Номер ответа: 2 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #2
|
Добавлено: 01.09.12 22:07
|
Я так полагаю, в коде присутствует что-то вроде
using (BinaryWriter writer = new BinaryWriter(innerStream))
{
....
}
После выхода из блока using у BinaryWriter вызывается метод Dispose, который, в свою очередь, вызывает Dispose для внутреннего потока. Это стандартное поведение (то же самое касается и других классов FCL, который работают как надстройка над другим потоком). Если в твоем случае это поведение не требуется, то просто убери блок using. При финализации BinaryWriter сборщиком мусора Dispose потока не будет вызываться.
Также учти, что если используешь какие-то Reader'ы (типа StreamReader), то корректную работу не получится обеспечить, так как Reader будет выбирать в свой внутренний кеш определенную порцию данных из потока. И возможна ситуация, когда данные считаны в буфер reader'а, уже пропали из потока, но не были считаны через методы *reader.Read..., и фактически будут утеряны.
Ответить
|
Номер ответа: 6 Автор ответа: Programmer
Вопросов: 71 Ответов: 246
|
Профиль | | #6
|
Добавлено: 01.09.12 22:25
|
- После выхода из блока using у BinaryWriter вызывается метод Dispose, который, в свою очередь, вызывает Dispose для внутреннего потока. Это стандартное поведение (то же самое касается и других классов FCL, который работают как надстройка над другим потоком). Если в твоем случае это поведение не требуется, то просто убери блок using. При финализации BinaryWriter сборщиком мусора Dispose потока не будет вызываться.
Мой объект IDisposable, в Dispose base не вызывается. Так же перегружен Dispose, вызова base.Dispose нет. Более того, логи говорят, что деструктор вызывается до Dispose, т.е. Dispose не вызывается вообще.
И нет, в обсуждаемом участке кода using не присутствует.
Ответить
|
Номер ответа: 8 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #8
|
Добавлено: 01.09.12 22:37
|
Если все так как вы говорите, и в вашем коде 100% нет ошибки, то поздравляю вас, вы нашли баг в .NET Framework. Идите на connect.microsoft.com и заводите багрепорт.
Правда вынужден вас огорчить, код, воспроизводящий проблему, туда все равно прийдется выложить. Плюс, с 95% вероятностью могу сказать, что редкий многопоточный баг с вероятностью 95% закроют как unable to reproduce (как закрыли мой, который, в отличие от вашего, 100% существует)
Ответить
|
Номер ответа: 11 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #11
|
Добавлено: 01.09.12 22:57
|
Programmer пишет:
Мое приложение создает 300 потоков
facepalm
Programmer пишет:
Я рассчитываю, что, возможно, кто-то сможет предположить, каким образом может происходит вызов ~ObjectName(), когда он "вот прямо сейчас" используется...
Если рассматривать этот вопрос абстрактно от конкретно моего кода, из-за чего такое может происходить?
Это не может происходить, так как если бы это происходило, то за 10 лет существования дотнета это поведение бы обнаружили и исправили проблему.
Или же вы нашли уникальный баг, который до этого никто не мог воспроизвести, тогда см выше.
Programmer пишет:
Удается воспроизвести только внутри "полезного" приложения, но оно слишком большое, чтобы выкладывать весь код.
Выложите проект где-нибудь вне форума.
Ответить
|
Номер ответа: 12 Автор ответа: Programmer
Вопросов: 71 Ответов: 246
|
Профиль | | #12
|
Добавлено: 01.09.12 23:08
|
facepalm Нужно было уточнить, что внутри присутствует Thread.Sleep(Random(...))?
Выложите проект где-нибудь вне форума.
Это коммерческое приложение, на такое я пойти не могу. Да, с этого "надо было начинать", но я бы выложил участок кода, если бы удавалось воспроизвести ошибку. Но не целый проект (а учитывая references - проекты).
Это не может происходить, так как если бы это происходило, то за 10 лет существования дотнета это поведение бы обнаружили и исправили проблему. Бывает и такое. Значит, ничего не поделаешь. Поставлю одним блоком try-catch больше и постараюсь не вспоминать
Ответить
|
Номер ответа: 13 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #13
|
Добавлено: 01.09.12 23:43
|
Programmer пишет:
Нужно было уточнить, что внутри присутствует Thread.Sleep(Random(...))?
Не нужно, это и так подразумевается. Даже если бы не было Thread.Sleep, ваши 300 потоков все равно большую часть времени занимались бы ожиданием в очереди (если, конечно, у вас нет 300-ядерного процессора)
Даже не говоря о том, что максимальную производительность можно получить тогда когда кол-во не больше кол-ва ядер ядер (при условии, что они все это время работают), стоит отметить что на каждый поток в оперативной памяти выделяется чуть более чем 1 мб под стек и контекст, в результате у вас отъедается 300+ мб оперативной памяти под потоки, поторые большую часть времени просто спят.
Programmer пишет:
Это коммерческое приложение, на такое я пойти не могу. Да, с этого "надо было начинать", но я бы выложил участок кода, если бы удавалось воспроизвести ошибку. Но не целый проект (а учитывая references - проекты).
Никто не собирается воспроизводить вашу ошибку. Выложите код класса в котором будет проблема, если вы там налажали это будет видно даже без его запуска.
Ответить
|
Номер ответа: 15 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #15
|
Добавлено: 02.09.12 00:04
|
Если вас интерисовал финализатор...
Финализатор может быть вызван после того как объект поставлен на очередь финализации, когда до него, собственно, дошла очередь (финализация объектов выполняется в отдельном потоке).
Постановка в очередь финализации происходит во время сборки мусора в том случае, если объект признан мусором, то есть на него нет ни одной живой ссылки (при этом допускается наличие слабых ссылок WeakReference, но если вы или ваши компоненты явно не используют этот класс, то этим можно пренебречь).
Возможна ли ситуация при которой вы обращаетесь к объекту и выясняется что он уже финализирован?
Да, возможна.
1) Так называемое "оживление".
Если объект имеет финализатор, то, как говорилось, после сборки мусора он помещается в очередь на финализацию, и НЕ УДАЛЯЕТСЯ из кучи. То есть сборка мусора завершилась, а объект остается в памяти, так как на него есть ссылка из очереди финализации. После того, как финализатор запустился, ссылка на объект удаляется из очереди на финализацию (при этом делается пометка, что данный объект уже был финализирован) и при следующей сборке мусора память, которую он занимал, будет освобождена физически (при дефрагментации кучи его место займут живые объекты).
Если же финализатор добавит ссылку на свой объект в какой-то корень (например, в какое-то статическое поле), то при следующей сборке мусора выяснится что объект уже не мусор, и он останется в памяти. И к нему можно будет обращаться по той ссылке, которую он создал. И фактически будет выполняться обращение к объекту, у которого уже срабатывал финализатор.
При этом, когда объект в очередной раз будет признан мусором, его финализатор уже не будет выполняться, так как на нем все еще стоит метка, свидетельствующая о том что он прошел процедуру финализации. Эту метку можно сбросить, вызвав GC.ReRegisterForFinalize. Тогда в следующий раз, когда объект будет признан мусором, для него опять будет выполнена финализация. И так, собственно, может происходить до бесконечности.
2. Ссылки между "мусорными" объектами.
Предположим, объект А ссылается на объект Б. Больше никто не ссылается ни на объект А, ни на объект Б. При сборке мусора оба объекта будут признаны мусором, даже не смотря на то, что на объект Б есть 1 ссылка. Причина, по которой объект признан мусором состоит в том, что ни до одного из этих объектов нельзя добраться из корней, с которых начинает работу сборщик мусора.
Допустим, у обоих объектов есть финализатор. Они оба ставятся в очередь финализации, и для обоих после этого запускается финализатор. Сначала финализатор выполняется для объекта Б, он проходит эту процедуру. Затем финализатор запускается для объекта А. В финализаторе он пытается обратиться по своей ссылке к объекту А. Произошло обращение к объекту, который прошел процедуру финализации.
Это происходит потмоу, что CLR не гарантирует какой-либо порядок запуска финализаторов объектов. То есть они запускаются в неопределенном порядке, и в финализаторе нельзя полагаться на то, что они запускаются в определенном порядке.
Кроме того CLR не гарантирует, что финализатор объекта вообще будет вызван, т.е. нельзя полагаться на то что финализатор вообще сработает, даже если сборщик мусора работает штатно.
По большму счету есть очень мало ситуаций, в которых приходится в класс добавлять финализатор (за последние 2 года, наверное, я не написал ни одного, даже забыл их синтаксис). Могу предположить, что в вашем случае их реализация связана с непониманием того как работает менеджер памяти в .NET
Ответить
|
Страница: 1 | 2 | 3 |
Поиск по форуму