Память и .Net
От: alexeiz  
Дата: 05.05.06 03:18
Оценка: 102 (9)
#Имя: FAQ.dotnet.memoryuse
По ходу этой дискуссии видно, что не все представляют, что находится в памяти .NET процессов. Из этого проистекают всякие разные домыслы и предположения, которые по большей части не имеют отношения к действительности. Поэтому я попытаюсь очень кратко показать, как посмотреть в память .NET процесса и определить, зачем она используется и по возможности, как её можно уменьшить.

Для примера я возьму уже обсуждавшееся приложение, в котором создаётся grid и заполняется 10000 строками данных. В моём случае я его ещё дополнил кнопкой "GC.Collect", чтобы вызывать сборку мусора, когда необходимо для тестовых целей. Визуально это выглядит вот так:



Код для приложения элементарен и выглядит так:
            for (int i = 0; i < 10000; ++i)
            {
                string [] rows = new string[3];
                rows[0] = i.ToString();
                rows[1] = "This is row " + rows[0];
                rows[2] = "Data for row " + rows[0] + " is " + rows[0];
                dataGridView1.Rows.Add(rows);
            }

Теперь приступим к анализу памяти (working set), требуемого этим приложением. Для начала посмотрим, что нам говорит Process Explorer (sysinternals.com): Working Set: 18896KB, Private Bytes: 20300KB. Эти цифры дают примерное представление о размере памяти, необходимой для процесса. Однако, по ним нельзя сказать для чего используется эта память, сколько в ней кода, данных, сколько потребовалось для GC, и т.д. Скудность этой информации даёт некоторым возможность предположить, что большая часть памяти требуется для GC, что GC выделил её с запасом, и он сможет её освободить и вернуть операционной системе, если потребуется. Чтобы понять действительно ли это так нам нужно получить больше информации о памяти нашего процесса.

Используем vadump (msdn.microsoft.com). Команда “vadump -sop <pid>” даёт нам очень красивое summary:
Category                        Total        Private Shareable    Shared
                           Pages    KBytes    KBytes    KBytes    KBytes
      Page Table Pages        40       160       160         0         0
      Other System            14        56        56         0         0
      Code/StaticData       2105      8420       540      2564      5316
      Heap                   214       856       856         0         0
      Stack                   16        64        64         0         0
      Teb                      4        16        16         0         0
      Mapped Data            121       484         0        28       456
      Other Data            2072      8288      8284         4         0

      Total Modules         2105      8420       540      2564      5316
      Total Dynamic Data    2427      9708      9220        32       456
      Total System            54       216       216         0         0
Grand Total Working Set     4586     18344      9976      2596      5772

Module Working Set Contributions in pages
    Total   Private Shareable    Shared Module
        5         2         3         0 wstest.exe
       58         3         0        55 ntdll.dll
       20         2         0        18 mscoree.dll
       48         3         0        45 KERNEL32.dll
       17         1         0        16 ADVAPI32.dll
       13         1         1        11 RPCRT4.dll
       31         2         0        29 SHLWAPI.dll
       34         2         1        31 GDI32.dll
       48         2         0        46 USER32.dll
       38         4         0        34 msvcrt.dll
       12         2         0        10 IMM32.DLL
        8         2         0         6 LPK.DLL
       36         6         0        30 USP10.dll
      257        14        21       222 mscorwks.dll
       53         6         1        46 MSVCR80.dll
       38         8         0        30 shell32.dll
       44         2         0        42 comctl32.dll
       17         4         0        13 comctl32.dll
      288        13        35       240 mscorlib.ni.dll
       32         4         0        28 ole32.dll
       25         2         0        23 uxtheme.dll
       21         3         0        18 MSCTF.dll
       30         4         0        26 DpOFeedb.dll
       69         2         0        67 mscorjit.dll
       67         6        15        46 System.ni.dll
       83         7        54        22 System.Drawing.ni.dll
      561        17       445        99 System.Windows.Forms.ni.dll
      125         7        62        56 gdiplus.dll
       22         2         0        20 msctfime.ime
        5         2         3         0 dciman32.dll

Необходимо отметить, что цифры Process Explorer’а и vadump’а немного отличаются, потому что они получены при разных запусках приложения.

Начнём анализировать. За код и статические данные отвечает следующая строчка:
Category                        Total        Private Shareable    Shared
      Code/StaticData       2105      8420       540      2564      5316

Она представляет из себя сумму по всем модулям процесса. Всего 8MB, т.е почти половина полной памяти не имеет отношения к GC, а является памятью, которую занимают модули. Причём всего 540KB является private для данного процесса. Остальная память либо разделяется между процессами (5316KB) либо может разделяться (2564KB). Посчитать память требуемую модулями .NET я оставлю как задачку для читателей. Модулями .NET являются: mscorwks, mscorjit, mscoree, и все модули оканчивающиеся на .ni.dll (NI – сокращённо native image – модуль, созданный NGEN’ом).

Теперь перейдём к GC. Где его heap? Память, используемая под GC Heap, vadump называет Other Data:
Category                        Total        Private Shareable    Shared
      Other Data            2072      8288      8284         4         0

Как видим, разделяемой памяти здесь нет, что очень даже логично. Всего мы имеем 8MB, что само по себе не так плохо, хотя и немало. Однако дальше в этом направлении продвинуться с помощью vadump’а сложно. В частности нельзя получить ответы на такие вопросы как: сколько из этой памяти реально используется объектами, а сколько было выделено GC наперёд для своей работы.

Дальше нужно двигаться с помощью windbg и модуля для отладки .NET процессов sos.dll (сокращённо от Son of Strike, в версии 1.0 этот модуль назывался strike).

Итак, запускаем “windbg -p <pid>” и загружаем sos:
0:004> .loadby sos mscorwks

Здесь можно набрать команду !help, чтобы узнать, какие возможности предлагает sos. Нас в данном случае интересует GC Heap, который мы посмотрим следующим образом:
0:004> !EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x01aacfe4
generation 1 starts at 0x018abfbc
generation 2 starts at 0x01381000
ephemeral segment allocation context: none
 segment    begin allocated     size
0016e7c8 7a721784  7a74248c 0x00020d08(134408)
0016a508 7b451688  7b467f9c 0x00016914(92436)
00155a98 790d6358  790f5800 0x0001f4a8(128168)
01380000 01381000  01b1eff4 0x0079dff4(7987188)
Large object heap starts at 0x02381000
 segment    begin allocated     size
02380000 02381000  02386d98 0x00005d98(23960)
Total Size  0x7fa850(8366160)
------------------------------
GC Heap Size  0x7fa850(8366160)

OK. Видно, что vadump нам не соврал. Действительно GC heap в данный момент отнимает ~8.4MB памяти. Однако, не весь GC Heap занят живыми объектами. Какая-то его часть свободна. Это можно узнать, пройдя по всем объектам в хипе:
0:004> !dumpheap -stat
total 232157 objects
Statistics:
      MT    Count    TotalSize Class Name
7b4c3800        1           12 System.Windows.Forms.DataGridViewColumnCollection+ColumnOrderComparer
7b47ef7c        1           12 System.Windows.Forms.SR
…
7b46d674    40543       648688 System.Windows.Forms.PropertyStore
7b4c7f0c    30006       960192 System.Windows.Forms.DataGridViewTextBoxCell
7b49bf58    30176       967352 System.Windows.Forms.PropertyStore+IntegerEntry[]
7b49c090    30260       973380 System.Windows.Forms.PropertyStore+ObjectEntry[]
00152a70    16158      1291624      Free
790fa3e0    36516      1829392 System.String
Total 232157 objects

Теперь понятно, что свободно у нас около 1.3MB. Это та память, которую GC может в принципе отдать OS, но он этого не сделает, потому как по сравнению с полным размером GC Heap (8.4MB) это очень мало. Для compacting GC нормально иметь вдвое больше памяти, чем требуется для живых объектов.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.