Pull to refresh

Comments 77

Ну почему же. Просто материал довольно заезженный (все читали в том или ином виде), к тому же… ну не то что бесполезный, но как-то перспективы не очень ясны — зачем вообще собственноручно управлять сборкой мусора ( не «как», а именно зачем).

На мой взгляд это вредно, потому как простые примеры, которыми обычно иллюстируют описание «как» — в реальности не требуют такого управления, а вручную управлять памятью в сложных системах — это как всунуть гвоздь в цилиндр работающего двигателя внутреннего сгорания — теоретически наверно возможно, но нужно чертовски хорошо понимать — что именно и зачем делается.

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

Лучше переведите вот эту
статью (и там были сопутствующие вроде), заодно расскажете всем — почему вручную управлять памятью в .NET можно но не нужно =).
Просто у меня было пара знакомых программистов, которые постоянно с этим работали с низкоуровневым программированием на C#. Чуть ли не CIL-коды прописывали.

> Лучше переведите вот эту
статью (и там были сопутствующие вроде), заодно расскажете всем — почему вручную управлять памятью в .NET можно но не нужно =)

Я это могу рассказать и без всякой статьи)
Могу ответить зачем. Затем что GC убирает мусор, но не обязан выполнять это сразу после выхода из области видимости. И неоднократно сталкивался в реальных приложениях, обрабатывающих большие массивы данных (сотни тысяч изображений, например), когда приложение начинает занимать по полгига и больше оперативы. Со сбором же памяти с использованием следующей функции объем занимаемой памяти не поднимался выше 10 мегабайт. Так что все зависит от задачи.

[DllImport("kernel32.dll")]
    private static extern bool SetProcessWorkingSetSize(IntPtr handle, int minimumWorkingSetSize, int maximumWorkingSetSize);
    public static void Collect()
    {
      GC.Collect();
      GC.WaitForPendingFinalizers();
      SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
    }
Это аналог вызова EmptyWorkingSet. Он не освобождает память. Просто урезает private working set — значение, которое по умолчанию показывает task manager.
Естественно, последующий своп и тормоза при этом обеспечены. Иметь на среднем десктопе 4Gb памяти, и при этом принудительно заставлять приложение свопить и не выходить за пределы 10 мегабайт — преступление.

Посмотрите статью habrahabr.ru/blogs/windows/107605/ — там в разделе task manager есть даже скрипт для «оптимизации всего подряд», вместе с обоснованием «полезности» такой «оптимизации».
Это задача была для машины где 512 мегабайт оперативы и до 150 параллельно исполняющихся задач. Но конечно я с вами согласен. В общем случае можно доверить сборщику. Но все таки бывает необходимость и в ручном вызове.
Необходимость в ручном вызове естественно бывает, иначе возможности его сделать просто не было бы. Но в таких жестких условиях просто вызова GC — мало. Нужно полноценное ужимание приложения — ngen для шаринга native images, MemoryFailPoint-ы, и полное понимание всех деталей работы GC. К сожалению, обычно ручная сброка мусора используется «на всякий случай».
«Сотни тысяч изображений» — а реализация IDisposable для изображения или для группы — никак?
Просто почему-то считается, что если уж управлять памятью — так через GC.Collect, все остальное — ниже достоинства программиста. Корректно реализовывать unmanaged взаимодействие — фи, пусть все делает фреймворк, а мы его принудительно попихаем за самую важную кнопку. А вы случайно при запросах SQL планы не подсказываете планировщику? А то 10 лет оптимизаций — не пошли ему на пользу, а вот ручное управление — наш выбор…
IDisposable.Dispose() не освобождает память, и вообще не имеет отношения к управлению памятью. Он освобождает остальные ресурсы, а память так и остается занятой до сборки мусора.
Я иногда подсказываю планировщику запросов в SQL. FORCESEEK в моем текущем проекте местами очень помогает против дедлоков при read committed snapshot. Это плохо?
=) Вы разбираетесь в том, что делаете — и это замечательно.
Но, как было уже упомянуто — большинство «программистов» — непрофессионалы. И в целом распространение и популяризация текстов (а на хабре это совсем не первая серия про GC) о ручном управлении памятью в .net скорее приведет к тому, что код будет скопипащен и размножен по проектам, чем к тому, что человек возьмет Рихтера, МСДН, Чена и разберется детально в том, что именно происходит и как управлять этим механизмом не навредив .NET.

Существует достаточно других способов сделать тяжелую задачу, распараллелить ее, раньше освобождать ресурсы и убирать из области видимости тяжелые объекты, но пусть GC вызывается тогда, когда посчитает нужным CLR, а не когда посчитает нужным некоторый premature оптимизатор. В общем для 99% случаев ручная сборка не нужна, не полезна и даже вредна. Имхо.
Согласен, любая статья о ручном сборе мусора должна начинатся с внушительного предупреждения «никогда так не делайте».
В следующей статье я расскажу, как можно собственноручно управлять процессом сборки мусора с помощью пространства имён System.GС.

Вот это поинтересней будет. Да впрочем, кому надо, тот знает, а кому надо будет, тот в мсдн полезит.
Все, что по этому поводу надо знать, написано у Рихтера, в CLR via C#. Зачем повторять?
Причем намного более подробно, и про финализаторы, и почему чтобы убрать весь мусор нужно вызвать:
GC.Collect();
GC.WaitForPendingFinalizers();
GCCollect();
И про дефрагментацию памяти, и про Large Objects Heap, которая умеет только расти.
На самом деле тема богатая, но изложена лишь небольшая и самая очевидная часть.
У Рихтера есть не всё, а только очень небольшая выжимка. Особенно если это 2-е издание на русском языке.
GCLatencyMode не упомянут вообще. Особенности работы с Pinned-объектами — тоже, насколько я знаю. Особенности для многоядерных систем — только вскользь, одним предложением. Для спокойной разработки Рихтера хватит. Для устранения проблем с памятью — скорее всего нет.
Так третья редакция еще в том году вышла.

«GCLatencyMode не упомянут вообще»
Полторы страницы.

«Особенности работы с Pinned-объектами — тоже, насколько я знаю.»
Тоже, прямо скажем, страница.

«Особенности для многоядерных систем — только вскользь, одним предложением.»
Сейчас там целый раздел про треды и режимы сборки мусора.

(и отдельно еще глава про трединг вообще)
Третья редакция не выходила на русском. А большинство разработчиков не читает книги на английском при наличии русского перевода. Да и русский перевод многие читают наискосок — посмотрите на любых форумах на обсуждения по Dispose/Finalize — сплошные мифы и домыслы. А ведь он тоже в Рихтере расписан, в той же главе.
Это говорит только о об их профессиональных данных. Таким и эта статья не поможет.
Некоторым достаточно статьи. Некоторым — недостаточно Рихтера.

Как ни грустно, но большинство разработчиков — непрофессионалы. Есть много студентов/джуниоров/просто не перечитывающих каждое издание Рихтера/Руссиновича/Робинса заново. Есть даже такие, что по it-тематике читают только хабр. Эта статья будет хотя бы на главной хабра, и в комментариях будет упоминаться Рихтер — уже этим она полезна.
«Некоторым достаточно статьи. Некоторым — недостаточно Рихтера. „
Речь идет о том, что глупо тратить время на статью, когда есть Рихтер.
А Вы считаете, что прямо все знаю об Рихтере?
Вот эту проблему и надо решать.

«Хотите узнать о GC — прочитайте соответствующую главу в последнем Рихтере» (21-ая для третьего издания)

Зачем пересказывать ее своими словами (да еще и с ошибками)?
Глупо тратить время на Рихтера, когда есть MSDN. Рихтер просто пересказывает его своими словами. 21-ю главу заменить парой ссылок. Проблема решена.
Покажите мне ту пару ссылок, я с удовольствием их почитаю.

(ключевое условие: по ссылке должен быть последовательно изложенный текст, то есть статья о вопросе, а не референс)
msdn.microsoft.com/en-us/library/0xy59wtx.aspx
Да, там надо пару раз по ссылкам пройтись. Но Рихтер с 70-ю страницами и 20+ разделами в главе тоже явно в формат статьи не впишется.
Я там в пару ссылок кликнул — у Рихтера подробнее.

Статьи бывают разной длины, речь не о размере, а о стиле изложения. Бывает справочник, а бывает статья.
… а бывает — книга. Я тут тоже открыл Рихтера Garbage Collection Modes — в MSDN и у Робинса подробнее. Так что ж теперь, Рихтер мне не нужен?
Это спор на пустом месте
Нельзя рассказать про всё и вся. Любая книга выпускается на среднего читателя. Даже с учётом узкоспециализированности какой-либо книги, есть моменты, которые нас хотелось бы видеть более глубоко, а есть те, про которые и читать особенно не хочется.
Ага, читая статью, прям чувствовал что-то знакомое. А это ж вырезка из Троэлсена.
Но все равно спасибо.
А я видел как один знакомый себе в ногу нож воткнул. Но я бы не стал рассказывать как именно надо это делать — ну потому что кто-то прочитает, скажет «ах, guru know...» и сделает именно это.

Ладно, тут похоже философский вопрос из серии — если ЯП позволяет простреливать себе коленки — надо ли всем программистам этим заниматься.

В общем, извините за занудство, вам наверное виднее, что надо писать. Только зачем тогда спрашиваете -«Не уж-то так плохо написал?»
Тогда посоветуйте, про что же такое Великое можно написать?
Да вроде как предлагал уже, но вы Реймонда Чена можете рассказывать и без всякой статьи. Но что-то я сильно сомневаюсь что вы этот вопрос задавали с намерением слушать ответы на него.
Про C#?
Сделайте обзор коллекций в стандартной библиотеке .NET 4.0, к примеру. Я не один и не два раза сталкивался с тем, что люди реализовывали (причем довольно криво) то, что можно было просто взять и использовать.
Кстати, вот это действительно реальный совет! Я как раз занимаюсь этим сейчас и с радостью напишу статью!)
Так же можете написать про неуправляемые ресурсы, там хватает заморочек.
Ну неуправляемые ресурсы очень разные бывают, что конкретно Вы имели ввиду?
Я бы про все виды неуправляемых ресурсов почитал бы. Но сам наткнулся на это дело при работе с файлами.
Сумбурно как-то. Об вот такие вещи "Не смотря на её название, это вовсе не означает, что вся сборка мусора теперь происходит в дополнительных фоновых потоках выполнения. На самом деле в случае фоновой сборки мусора для объектов, не относящихся к эфемерному поколению, исполняющая среда .NET теперь может проводить сборку мусора объектов эфемерного поколения в отдельном фоновом потоке." можно мозг сломать. Вроде бы и понятно, а над каждым вторым предложением приходится медитировать, вчитываясь в предыдущие и выстраивая логическую цепочку из высказываний. В данном конкретном примере во втором предложении явно не хватает слова «только» и лишнее слово «теперь» — спотыкаешься и начинаешь думать «по сравнению с чем теперь?» и «а что же с остальными поколениями?». И таким моментов много.
А в целом интересно даже на теме, про которую тут уже много раз писали.
в третьем предложении, а не во втором (первое предложение из цитаты лишнее и скопипастилось случайно)
Спасибо, интересно было почитать. Самому не так давно приходилось с этим заморачиваться, но только, чтобы работать с оборудованием через неуправляемую библиотеку. А вообще, автоматическое управление памятью — это такой кайф. ^_^
Позвольте с вами не согласиться. С одной стороны явный плюс с точки зрения performance и «легкого» исключения человеческого фактора. С другой стороны, лично я, не могу чувствовать себя комфортно не используя в ряде случаев ручного управления.
naum, я с Вами полностью согласен, но надо разделять то, где надо этим пользоваться, а где лучше не лезть.
Исправьте меня, если не прав, но даже в случае «перехода» на полностью ручное высвобождение памяти никаких проблем не возникает (за исключением небольшой деградации performance по сравнению с автоматическим режимом и, как уже было выше сказано, возможных «ошибок», приводящих к утечкам памяти).
В данном случает я могу сказать только одно: «Все мы люди». Совершенных программ не бывает. Лично я не вижу смысла переходить на ручное управление там, где это просто не надо.
Человеческий фактор понятен, тут нет вопросов. По остальным моментам тоже согласен. Я всю эту демагогию развел только по той причине, что .Net для меня немного в новинку в плане сборки мусора после Delphi/C++ и в силу привычки к методике написания кода, когда любое порождение сразу же обрамляется высвобождением, мне иногда тяжело перестроиться.
Не перестраивайтесь. В .net более чем достаточно IDisposable-классов, которые нужно освобождать руками.
Хорошая обзорная статья, но не более.
Несколько замечаний:
1. > Идентифицирует объект, который уже «пережил» один процесс сборки мусора (был помечен, как надлежащий удалению, но не был удалён из-за достаточного свободного места в куче).

Мне кажется, в скобках не хватает слов «в том числе» — так как объект может пережить сборку мусора, и не будучи помеченным как мусор.

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

3. Вы не сказали, что будет, если даже после очистки 2го поколения памяти не хватит.
Про финализацию я собирался написать в продолжении этой статьи. Но судя по всему, она никому не понадобится, так что я не вижу смысла для себя заморачиваться.

Спасибо Вам за поправки, потому что это моя первая статья и она по определению не могла быть идеальной)
Я просто к тому, что если что-то упоминается в статье — лучше это хоть как-то объяснить. Для более цельной картины.
Внеси, пожалуйста, в статью вышеуказанные поправки, особенно из пункта 1, а то у тебя получается что мусор может дойти аж до второго поколения.

А вообще в джаве интереснее читать статьи про сборку мусора, перлы вроде «Большинство объектов умирают молодыми в раю» доставляют. И названия поколений более понятные:

0 — Young, Eden: молодое поколение, Рай
1 — Survivor: оставшиеся в живых
2 — Old, Tenured: старые, штатные объекты
>>Вы не сказали, что будет, если даже после очистки 2го поколения памяти не хватит.
Вылетит OutOfMemoryException или InsufficientMemoryException
Да, я знаю. Но, как мне кажется, об этом стоит сказать в статье. Особенно, при каких условиях какой Exception вылетает.
Примеров хотелось бы побольше. А то получился совсем уж откровенный пересказ Троелсена.
Я просто слабо представляю, какие примеры можно здесь привести здесь.
А вы прочитайте (или перечитайте) главу у Рихтера — там как раз в связке с финализатором и слабыми ссылками предлагается реализация кэширования.
Финализация и кэширование больше идёт к теме управления нежели к тему об общем принципе работы сборщика мусора. Я могу рассказать, как это реализуется на уровне CIL но надо ли это?
Не, я не к тому, чтобы слепо копировать примеры.
Просто с этого ракурса больше про финализацию станет понятно.
Ощущение что статья чисто для галочки на itbonus или чем-то подобном. Куча воды, практически 0 полензной информации и куча нюансов упущена. Не рассказанно про вторую кучу, совершенно не упомянуто ни одих грабель, которые приводят к утечкам памяти в .net. Если все-таки «золотое правило» в кавычках именно потому что оно наоборот — вредное — то нигде в статье этого не говорится, если же действительно полагается, что все объекты, ссылки на которые вы больше никак не можете получить — удалятся — то это непонимание принципов. В .net утечки памяти могут возникнуть (bingo!) даже если все объекты действительно уже финализированы и удалены.
Далее — когда говорят «стал оптимальным» все же подразумевается, что лучше некуда, а тут лучше есть куда, еще очень и очень много куда развиватсья есть.

Честно — даже на обзорную статью не тянет, уж слишком много концептуальных кусков опущенно, описанно по верхам и всего пара аспектов.

Ну и в конце концов, когда вы пишете «new smth()» — это совсем не значит что объект попадет вообще хоть в какую-то кучу.
>>Использование ключевого слова new приводит к добавление объекта класса в так называемую управляемую кучу, а назад возвращается ссылка на объект.
У value-типов такого не происходит.
Если уж совсем залезть в дебри, то инструкция newobj кладет новый объект (вне зависимости от типа) в стек. Уже после объект берется из стека и т.д.
я хотел написать про newobj, но подумал, что это слишком
UFO just landed and posted this here
Ухты, интересно, я только начал писать на шарпе, и столкнулся с непонятными для меня, назовем их «багами», связанными со сборщиком мусора, а вот оказывается как оно бывает. Спасибо за информацию (и о новой редакции книги тоже).
Вы уверенны что это именно те «баги»? Просто именно аспекты сборки мусора (если все остальное сделанно правильно) приводят только к утечкам памяти, а их «багами» обычно не называют.
Я поэтому и взял в кавычки, не совсем конечно уверен что, как и где (я в процессе понимания), но одно знаю точно — объект зачищали без моего указания и ведома. И я не понимал почему я вижу исключение которого быть не должно.
Проще выложите код, если там никакой сверх-магии нет — напишу причину бага.
Не знаю уместно ли тут обсуждать конкретный код, но в общем суть в чем — нужно хранить глобальный массив форм, что бы из любого места всем они были доступны. Для этого я использовал Dictionary<string, MyForm>, ну и почему-то при добавлении очередной формы в массив она просто уничтожалась и при попытке её показать возникало ObjectDisposedException.
Разве такое возможно вообще? C# запрещает хранить глобальные объекты
Да нет, в общем то, не запрещает. Просто хранятся они в скопе статического класса.
ObjectDisposed — это не «уничтожалась», это значит что кто-то вызвал у формы метод Dispose (что к GC не имеет отношения). С формой такое может происходить если вы вызываете у объекта формы Close(), а потом пытаетесь показать его.
Ну или если форма любым образом получает сообщение о закрытии, напр. WmClose. Или если система вызывает у нее Dispose.
Ага, интересненько, правда Close вроде бы никто не вызывает, а пока сделал проверку isDisposed, но вообще надо будет подумать где я накосячил…
Сделайте в своем классе override Dispose(bool) (он выше по иерархии объявлен как виртуальный и вызывается из обычного Dispose() класса Component()) и поставьте там точку останова, потом пошаговой отладкой поймаете, кто вызывает.
Да, только для этого желательно включить отладку .net классов (ну или хотябы глянуть стек вызовов), иначе не увидите, если по стеку нет вашего кода.
Спасибо за подсказку, буду знать на будущее
Sign up to leave a comment.

Articles