По ходу этой дискуссии видно, что не все представляют, что находится в памяти .NET процессов. Из этого проистекают всякие разные домыслы и предположения, которые по большей части не имеют отношения к действительности. Поэтому я попытаюсь очень кратко показать, как посмотреть в память .NET процесса и определить, зачем она используется и по возможности, как её можно уменьшить. Для примера я возьму уже обсуждавшееся приложение, в котором создаётся grid и заполняется 10000 строками данных. В моём случае я его ещё дополнил кнопкой "GC.Collect", чтобы вызывать сборку мусора, когда необходимо для тестовых целей. Визуально это выглядит вот так: Код для приложения элементарен и выглядит так:
Теперь приступим к анализу памяти (working set), требуемого этим приложением. Для начала посмотрим, что нам говорит Process Explorer (sysinternals.com): Working Set: 18896KB, Private Bytes: 20300KB. Эти цифры дают примерное представление о размере памяти, необходимой для процесса. Однако, по ним нельзя сказать для чего используется эта память, сколько в ней кода, данных, сколько потребовалось для GC, и т.д. Скудность этой информации даёт некоторым возможность предположить, что большая часть памяти требуется для GC, что GC выделил её с запасом, и он сможет её освободить и вернуть операционной системе, если потребуется. Чтобы понять действительно ли это так нам нужно получить больше информации о памяти нашего процесса. Используем vadump (msdn.microsoft.com). Команда “vadump -sop <pid>” даёт нам очень красивое summary:
Необходимо отметить, что цифры Process Explorer’а и vadump’а немного отличаются, потому что они получены при разных запусках приложения. Начнём анализировать. За код и статические данные отвечает следующая строчка:
Она представляет из себя сумму по всем модулям процесса. Всего 8MB, т.е почти половина полной памяти не имеет отношения к GC, а является памятью, которую занимают модули. Причём всего 540KB является private для данного процесса. Остальная память либо разделяется между процессами (5316KB) либо может разделяться (2564KB). Посчитать память требуемую модулями .NET я оставлю как задачку для читателей. Модулями .NET являются: mscorwks, mscorjit, mscoree, и все модули оканчивающиеся на .ni.dll (NI – сокращённо native image – модуль, созданный NGEN’ом). Теперь перейдём к GC. Где его heap? Память, используемая под GC Heap, vadump называет Other Data:
Как видим, разделяемой памяти здесь нет, что очень даже логично. Всего мы имеем 8MB, что само по себе не так плохо, хотя и немало. Однако дальше в этом направлении продвинуться с помощью vadump’а сложно. В частности нельзя получить ответы на такие вопросы как: сколько из этой памяти реально используется объектами, а сколько было выделено GC наперёд для своей работы. Дальше нужно двигаться с помощью windbg и модуля для отладки .NET процессов sos.dll (сокращённо от Son of Strike, в версии 1.0 этот модуль назывался strike). Итак, запускаем “windbg -p <pid>” и загружаем sos:
Здесь можно набрать команду !help, чтобы узнать, какие возможности предлагает sos. Нас в данном случае интересует GC Heap, который мы посмотрим следующим образом:
OK. Видно, что vadump нам не соврал. Действительно GC heap в данный момент отнимает ~8.4MB памяти. Однако, не весь GC Heap занят живыми объектами. Какая-то его часть свободна. Это можно узнать, пройдя по всем объектам в хипе:
Теперь понятно, что свободно у нас около 1.3MB. Это та память, которую GC может в принципе отдать OS, но он этого не сделает, потому как по сравнению с полным размером GC Heap (8.4MB) это очень мало. Для compacting GC нормально иметь вдвое больше памяти, чем требуется для живых объектов. |