воскресенье, 2 июня 2013 г.

Поиск утечек памяти в приложениях WPF (перевод)

Автор: Jossef Goldberg
Название оригинала: Finding Memory Leaks in WPF-based applications
Дата публикации: 5-2-2008 5:23 AM
Адрес источника: http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx

+ [Дополнения] от переводчика (niknolaty.blogspot.com)

Существует множество блогов, которые объясняют утечки памяти в программах Microsoft .NET Framework с управляемым и неуправляемым кодом.

В этом посте я хочу:

    1) Показать примеры кода, которые могут вызвать утечки памяти, возникающие в
     WPF приложениях;

    2) Разместить информацию об утечках памяти в .NET Framework

    3) Показать, как можно предотвратить эти утечки;

    4) Обсудить инструменты и техники, позваляющие обнаружить утечки.

Я планирую обновлять данный пост большими примерами кода, поскольку мы продолжаем поиск дополнительных утечек платформы и примеров кодирования, которые вызывают утечки памяти в WPF-приложений.

Пример

Для иллюстрации я прикрепил пример приложения. Приложение содержит дочерние окна, каждое из которых разделено на определенный тип утечки памяти. В каждом из случаев закрытия этого окна, не происходит ожидаемого освобождения памяти, занимаемым объектом Window.


Для наглядности, я включил таблицу с типами утечек:

Описание утечки
Ошибка разработчика
NET 3.0 
NET 3.5 
NET 3/5 sp1
Неправильное использование обработчиков событий
Да



Неправильное использование привязки данных
Да



Неправильное использование привязки команды
Да
Да
Да
Да
Неправильное использование статических обработчиков событий
Да



Использование BitmapImage в источнике Image

Да
Да

Множественное использование BitmapImage


Да

Использование скачанного BitmapImage

Да


Утечки в CMilChannel при инициализации HWND, разрушенного под Windows XP

Да (XP)
Да (XP)
Да (XP)
ShutDownListener делает утечку для каждого потока, созданного через привязку

Да
Да
Да
Создание и уничтожение WriteableBitmap на Windows XP через аппартный рендеринг



Да (XP с HW)
Viewport3D, VisualBrush, WriteableBitmap, и др., утечки в Windows XP

Да (XP с HW)

Утечка памяти

Чтобы посмотреть утечку памяти:

    1) Запустите Process Explorer. Откройте диалог "Свойства процесса" (правый щелчок мыши/Свойства).

    2) Откройте несколько дочерних окон.

    3) Обратите внимание, память возрастает на 50 Мб при каждом запуске.

    4) Закройте диалог без флажка не чекбоксе ("Очистка событий для избежания утечки памяти").


    5) Щелкните на "Force GC" чтобы показать список мусора.

    6) Обратите внимание, память не освобождена.

    7) Повторите шаги (4) и (5), но теперь с флажком не чекбоксе. Это позволит
освободить объекты, когда закроется окно. На данный момент память освобождена.


У каждого из дочерних окон получается утечка памяти по причинам, описанных ниже.

1. Использование обработчика событий


Причина

Утечка срабатывает, потому что дочернее окно Window2 имеет ссылку (зарегестрированую как событие) на окно Window1.TextBox1, которая остается висеть в памяти, вызывая объект Window2 и элемент в дереве остается живым.

В общем, если Вы напишете так:

Foo.SomeEvent += new EventHandler(Bar.SomeMethod)
То, когда Вы закончите использовать Bar, но будете использовать Foo, Bar также останется жив. Наверное это не то, что Вы могли ожидать. 

Код:
Window1.w1.TextBox1.TextChanged += new TextChangedEventHandler(this.TextBox1_TextChanged);
Объект Window2 будет оставаться в памяти, пока TextBox1 в Window1 будет жить.

Исправление/Решение


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

Пример:
Window1.w1.TextBox1.TextChanged -= new TextChangedEventHandler(TextBox1_TextChanged);
Второй подход заключается в создании своего рода трюка (например, "Слабые ссылки"). См. блог Грега Шехтера (Greg Schechter's) для примера.

[Дополнение: в WPF есть два события закрытия. Событие Closing() происходит непосредственно после нажатия на кнопку Close и может отменить закрытие окна. Событие Closed() происходит, когда окно закрывают.]

2. Использование Data Binding


Причина

Данная утечка описана в статье KB. Она срабатывает потому что:

TextBlock имеет Binding к объекту MyGrid, который ссылается обратно на TextBlock (это один из потомков MyGrid). Обратите внимание, что этот тип утечки DataBinding является уникальным для конкретного сценария и для сценария привязки в частности, как описано в статье KB. Свойство в Path не является свойством зависимости и не является классом, который реализует INotifyPropertyChanged и, кроме того, должна существовать цепь прямых ссылок.


Код:
myDataBinding = new Binding("Children.Count");
myDataBinding.Source = myGrid; 
myDataBinding.Mode = BindingMode.OneWay;
MyTextBlock.SetBinding(TextBlock.TextProperty, myDataBinding);
Похожий код можно записать на XAML:
<TextBlock Name="MyTextBlock" Text="{Binding ElementName=myGrid, Path=Children.Count}" />
Исправление/Решение

Есть несколько подходов, самый простой - это очистить привязку (binding), когда окно закроется.

Пример
BindingOperations.ClearBinding(MyTextBlock, TextBlock.TextProperty);
Другой подход - это установить режим привязки в OneTime. Подробней можно посмотреть в статье KB.

[Дополнение: Режим привязки OneTime сообщает о том, что привязка будет осуществляться один раз при запуске программы, либо при смене контекста данных (DataContext). Это более простая форма привязки типа OneWay, она обеспечивает более высокую производительность в тех случаях, когда значение источника не меняется.]

3. Использование привязки команды


Данная утечка срабатывает, потому что объект Window2 добавляет привязку команды к окну Window1. В WPF привязка команды использует прямую ссылку, которая вызывает окно Window2, а дочерней объект еще не реализован, пока Window2 остается в памяти.

Код:

command = new RoutedCommand("ClearBox", this.GetType());
command.InputGestures.Add(new KeyGesture(Key.F5));
myCmdBinding = new CommandBinding(command, F5CommandExecute);
Window1.w1.CommandBindings.Add(myCmdBinding); //добавление привязки к Window1
Заметка: Вероятно, это не является распространенной практикой, но данный код написан при условии, чтобы продемонстрировать идею использования привязки команды, которая может привести к утечкам памяти.

Исправление/Решение

Самым простым решением будет вызов очистки CommandBinding, когда окно будет закрыто.

Пример

Window1.w1.CommandBindings.Remove(myCmdBinding);
4. Использование статического обработчика событий


Причина

Данная утечка срабатывает, потому что дочернее окно (Window2) имеет ссылку (зарегестрированую как событие) на статическое событие. Поскольку объект является статическим, объект Window2 никогда не будет освобожден.

Код:

Application.Current.Activated += new EventHandler(App_Activated);
Исправление/Решение

Просто отменяем регистрацию события с объекта Window2, когда окно будет закрыто.


Пример
Application.Current.Activated -= new EventHandler(App_Activated);
Второй подход был указан ранее в пункте [1].

5. Использование BitmapImage в Image Source


Причина

Утечка срабатывает потому, что WPF сохраняет прямые ссылки между статическим BitmapImage (bi1) и Image (m_Image1). BitmapImage (bi1) определен как статический, поэтому он не очищается сборщиком мусора, когда окно Window2 будет закрыто, так как под капотом WPF хуки событий привязаны к BitmapImage (например событие DownloadFailed), а он вызывает изображение m_Image1, чтобы остаться в живых. Это, в свою очередь, заставляет все дерево Window2 висеть в памяти даже после того, когды Вы закрыли окно. Утечка может произойти только тогда, когда вы используете BitmapImage, она не появляется, когда используется DrawingImage.

Данная проблема исправлена в следующей версии .NET сервис пака (.NET 3.5 SP1).


Код:
// bi1 статический
bi1 = new BitmapImage(new Uri("Bitmap1.bmp",UriKind.RelativeOrAbsolute));
  
// bi1.Freeze() // если не использовать Freeze, в Вашем приложение будет утечка памяти

m_Image1 = new Image();
m_Image1.Source = bi1;  
MyStackPanel.Children.Add(m_Image1); 
Исправление/Решение

Решение может зависит от вашего сценария. Один из способов - это использовать функцию Freeze() для BitmapImage.

WPF не перехватывает события для объектов, которые заморожены. Данное решение может использоваться, если Вы нажмете на второй чекбокс, расположенный выше. Другое решение - это клонировать (Clone()) BitmapImage или сделать его статическим. В общем, Вы должны морозить объекты по возможности, чтобы улучшить производительность приложения. Подробности смотрите здесь.

Пример

bi1.Freeze();
6. Множественное  использование BitmapImage в Image Source


Причина

Данная утечка была упомянута выше. Она возникает, потому что WPF сохраняет прямые ссылки между статическим BitmapImage bi1 и изображением m_Image1.

Когда изображению Image присваивается новый источник (например: m_Image1.Source = bi2;), WPF "забывает" удалить предыдущее "старое" событие, которое подключали к bi1. Опять же, поскольку bi1 является статическим и не удаляется сборщиком мусора, он заставляет изображение оставаться висеть в памяти, который и вызывает утечку.

Данный вопрос был поднят в .NET версии 3.5. Он не актуален в .NET 3.0.
Эта проблема будет исправлена в следующей версии .NET сервис пака (.NET 3.5 SP1).

Код:

static BitmapImage bi1 = new BitmapImage(new Uri("Bitmap1.bmp", UriKind.RelativeOrAbsolute));
static BitmapImage bi2 = new BitmapImage(new Uri("Bitmap2.bmp", UriKind.RelativeOrAbsolute));
…

if (bi2.CanFreeze)    
      bi2.Freeze();

// bi1.Freeze() // даже если используете для bi2 источник изображения, 
                // Вы также должны заморозить bi1

m_Image1 = new Image();
m_Image1.Source = bi1;      // использование не замороженного bitmap - вызовет утечку
m_Image1.Source = bi2;      // используйте замороженный bitmap
MyStackPanel.Children.Add(m_Image1);
Исправление/Решение

Чтобы решить данную проблему, можно не использовать код в вышеупомянотом виде, либо заморозить все остальные BitmapImage:

bi1.Freeze();
7. Использование скачанного BitmapImage в Image Source


Данная утечка срабатывает, потому что WPF не удаляет внутренние ссылки на некоторые объекты (например, LateBoundBitmapDecoder, BitmapFrameDecode, и т.д.), которые используются во время загрузки с интернета. Утечка срабатывает только тогда, когда Вы скачиваете изображение с интернета (это не актуально для изображений, находящихся на локальной машине). Эта проблема будет исправлена в следующей версии .NET сервис пака (.NET 3.5 SP1).

Чтобы отследить утечку, Вы можете открыть вышеупомянутое окно, закрыть его, и щелкнуть на кнопке "Force GC", чтобы увидеть список мусора.

При запуске команды в WinDbg, расположенной ниже, Вы заметите объекты, которые остались в куче (Heap). Это объекты, которые вызывают утечку и закрепляются за объектом Image и всего дерева после закрытия окна Window2.

.loadby sos mscorwks
!DumpHeap -type System.Windows.Media.Imaging
 
53dadf18   6   72 System.Windows.Media.UniqueEventHelper`1 [[System.Windows.Media.Imaging.DownloadProgressEventArgs, PresentationCore]]
53da4374   1  108 System.Windows.Media.Imaging.PngBitmapDecoder
53da09e0   4  112 System.Windows.Media.Imaging.BitmapSourceSafeMILHandle
53d8d2f0   1  120 System.Windows.Media.Imaging.LateBoundBitmapDecoder
53da0524   1  172 System.Windows.Media.Imaging.BitmapFrameDecode
53da89c8   3  648 System.Windows.Media.Imaging.BitmapImage
Код:
// Утечка сработает, когда будете использовать BitmapImage, загруженного с интернета
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(@http://www.somesite.com/some_image.png, UriKind.RelativeOrAbsolute);
image.CacheOption = BitmapCacheOption.OnLoad;
image.CreateOptions = BitmapCreateOptions.None;
image.EndInit();

m_Image1 = new Image();
m_Image1.Source = image;   
MyStackPanel.Children.Add(m_Image1);
Исправление/Решение

Обойти ее можно, осуществляя загрузку BitmapImage в начале во временную папку или в память, а затем использовать локальное изображение BitmapImage. Смотрите (
WebClient.DownloadFile & WebClient.DownloadData).

8. Утечки в CMilChannel при инициализации HWND, разрушенного под XP


Причина

Это утечка в WPF присутствует во всех версиях фреймворка вплоть до .NET 3.5 SP1. Это происходит из-за того, как WPF осуществляет выбор, а именно: какой HWND использовать для отправки сообщений от визуализации потока в поток пользовательского интерфейса. Этот пример разрушит первый созданный HWND и запустит анимацию в новом окне. Он вызывает сообщения, отправленные на поток визуализации без обработки, что создает утечку памяти.

Исправление/Решение

Выходом может быть создание нового HwndSource первым в конструкторе класса App. Он должен быть создан до того, как будет создаваться любой другой HWND. Просто создавая этот HwndSource, WPF будет использовать его для передачи сообщений от визуализации потока в поток пользовательского интерфейса. Это гарантирует, что все сообщения будут обработаны.

Примечание: Эта проблема довольно таки редка, поэтому реализовывайте данное решение, если Вы на самом деле испытываете из-за этого проблемы.


9. ShutDownListener делает утечку для каждого потока, созданного через привязку

Причина

Это утечка в WPF присутствует во всех версиях фреймворка вплоть до .NET 3.5 SP1. Она происходит из-за того, что обработчик события в движке Data Binding'a подключен, но никогда не снимается, когда привязка будет использоваться в новом потоке. В этом примере создается некоторое число новых потоков, и для каждого нового окна используется привязка данных.

Исправление/Решение:

Пока недоступно


10. Создание и уничтожение WriteableBitmap на XP через аппартный рендеринг


Причина

Эта утечка присутствует только в WPF версии 3.5 SP1. Она происходит всякий раз, когда создается WriteableBitmap и разрушается на Windows XP с использованием аппаратного рендеринга. Данный пример неоднократно создает и разрушает, обновляет и показывает новые WriteableBitmap'ы, что приводит к утечке памяти.

Исправление/Решение

Для окна, содержаший WriteableBitmap, установить
HwndTarget.RenderMode в RenderMode.SoftwareOnly.

11. Viewport3D, VisualBrush, WriteableBitmap, и др., утечки в Windows XP




Причина

Эта утечка присутствует в WPF
версии 3.5 SP1. Она происходит, когда VisualBrush, WriteableBitmap, или любые другие объекты используются в Viewport3D в программном режиме рендеринга.

Исправление/Решение

По возможности, используйте аппартный рендеринг. Если же он не доступен, и Вы подозреваете, что данная утечка есть, попробуйте заменить Вашу кисть на SolidColorBrush, чтобы посмотреть, пропадет ли утечка. Если она не устранена, то значит есть другая. А если же она исчезла, то просто используйте другую кисть, которая не будет содержать утечки памяти. Другой способ отсутсвует.


[Дополнение: Код статьи - KB967634. Исправлено в WPF 4.0 (ссылка)]

[Дополнение] 12. Использование словарей ресурсов

Причина

Если в приложении используется словарь ресурсов (ResourceDictionary) либо словарь тем, то, так как они являются глобальными, после их использования они могут остаться висеть в памяти, когда приложение закроется.

Исправление/Решение

Дискуссия по этому поводу была здесь. Данный баг был исправлен в WPF 4.0. Код статьи - KB967328 (ссылка).


[Дополнение] 13. Ресурсы, не использующие фактических значений

Причина

Данная утечка относится к приложению, которое содержит стили/шаблоны, определенные в ресурсах (StaticResource или DynamicResource) и при этом, создается большое количество элементов, не использующие фактические значения данного ресурса. В данной ситуации, у приложения может срабатывать утечка объектов DeferredAppResourceReference, даже когда они были очищены. Кроме того, в приложении может срабатывать утечка типа WeakReferences.

Например, в ресурсах App.xaml определяют:

<sys:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">20</sys:Double> 
Этот ресурс используют любые элементы управления, которые могут отображать горизонтальную полосу прокрутки. Создание большого количества таких элементов управления, которые на самом деле никогда не будут показывать эту прокрутки и вызовет утечку.

Исправление/Решение

Данный баг был исправлен в WPF 4.0. Код статьи - KB981107 (ссылка).

[Дополнение] 14. Котороткоживущие элементы, связанные с DependencyProperty

Причина

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

Например: ListBoxItem связывает свое свойство HorizontalContentAlignment с аналогичным свойством в своем ListBox. Приложение, которое создает большое количество ListBoxItem (скажем, делая большое количество добавлений и удалений в ItemsSource), будут сталкиваться с утечкой памяти.

Исправление/Решение

Данный баг был исправлен в WPF 4.0. Код статьи - KB981107 (ссылка).

[Дополнение] 15. Классы с элементами, использующие KeyboardNavigation

Причина

Эта проблема касается классов, производных от TreeView или Selector (в том числе ListBox, ListView, Menu, DataGrid, и многих других). Эти классы зарегестрированы на внутренние уведомления от класса KeyboardNavigation, который используется для управления фокусом клавиатуры. Приложение, которое создает большое количество таких элементов, является уязвимым к утечке памяти объектов типа WeakReference и массива пространства, для их хранения. Например: если приложение создает большое количество короткоживущих ListBox'ов, то Вы непременно столкнетесь с утечкой. Это может произойти во вложенном сценарии, где в ListBox отображается коллекция, в которой происходит большое количество операций удаления и добавления - и все это отображается внутри ListBox.

Исправление/Решение

Данный баг был исправлен в WPF 4.0. Код статьи - KB981107 (ссылка).

Отладка утечек памяти

В поисках утечек памяти, я в основном использовал CLR Profiler для .NET Framework 2.0 и WinDbg. Преимущество еще и в том, что они оба являются бесплатными.

Полезные советы:

Гораздо легче обнаружить утечку, если специально сделать объект очень большим. Например: добавьте 50 Мб к его размеру, и Вы сможете наблюдать признаки утечки памяти. В моем примере я выделил примерно 50 Мб памяти в каждом дочернем окне (Byte[]).

Если у вас есть небольшая утечка, то может потребоваться много итераций, прежде чем сделать вывод, действительно ли она существует, используя Process Explorer или диспетчер задач.


Заставляя сборщик мусора освободить память, Вы получаете разницу между объектами и можете определить, у которых есть утечка, а у которых нет. Пример кода:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Использование сборщика также полезно, когда вы осмотриваете память, например с помощью Process Explorer, и одновременно используете CLR Profiler - это позволит использовать GC между каждым снимком кучи (Heap).

Использование CLR Profiler

    1. Запустите CLR Memory Profiler под админом на Висте


    2. Уберите флажки с чекбоксов "Allocations", "Calls" и "Profiling Active"


    3. Нажмите "Start Application" и перейдите к точке, где Вы готовы быть до снимка кучи
(Heap)

    4. Щелкните "Show Heap Now"


    5. Теперь установите флаги "Profiling Active" и "Allocations" для включения
профилирования

    6. Запустите и закройте окно с утечкой (например: Event "Handler test")


    7. Выберите кнопку "Show Heap Now"


    8. Щелкните правой кнопкой мыши на последнем графике и щелкните "Show New
Objects"



Теперь Вы видите, что обработчик TextChangedEventHandler остановился с размером 50 Мб буфера Byte[]:


Повторите данный процесс для окна "Command Binding test", и он покажет 50 Мб для объекта CommandBinding. Смотрите на скрин:


Использование WinDBG

В большей степени, я следовал инструкциям, опубликованных здесь.
windbg -p <your process id>

0:004> .loadby sos mscorwks
Я выполнил:
0:005> !DumpHeap –stat
Дважды (перед и после утечки).
"!DumpHeap –stat" покажет состояние кучи (Heap) перед срабатыванием утечки:


…

5695e56c      460        18400 System.Windows.DependencyProperty
5696975c      188        20280 System.Windows.EffectiveValueEntry[]
79135df4       99        34440 System.Reflection.CustomAttributeNamedParameter[]
0056ed60      297        37656      Free
7913b600      177        65376 System.Collections.Hashtable+bucket[]
7912b884     3307       152020 System.Object[]
790fc6cc     8516       455296 System.String

Total 32362 objects
После утечки команда "!DumpHeap –stat" покажет следующее:
5543b1e8      189        11340 System.Windows.Markup.BamlAttributeInfoRecord
53d0d3ac       40        11424 System.Windows.ClassHandlers[]
569698f4      384        11888 MS.Utility.FrugalMapBase+Entry[]
790febbc      627        12540 System.RuntimeType
5695e7c0      628        12560 System.Windows.DependencyProperty+FromNameKey
5696975c      244        15928 System.Windows.EffectiveValueEntry[]
5542d18c      676        16224 System.Windows.FrameworkPropertyMetadata
5695e56c      484        19360 System.Windows.DependencyProperty
7913b600       80        38952 System.Collections.Hashtable+bucket[]
7912b884      785        73608 System.Object[]
0056ed60      288       103380      Free
790fc6cc     7218       373856 System.String
7913b858       57     52433700 System.Byte[]
Подозрения на размещение буфера Byte[] размером 50 Мб на лицо. Позже я выполнил:
0:005> !dumpheap -type System.Byte[]

…

013894d4 7913b858       60     
0138965c 7913b858      228     
013897c0 7913b858       60     
01389a70 7913b858       60     
0138f6d4 7913b858      500     
06dc1000 7913b858 52428816     

total 57 objects

Statistics:

      MT    Count    TotalSize Class Name

7913b858       57     52433700 System.Byte[]

Total 57 objects
Потом я выполнил команду gcroot с адресом на наш буфер и WinDbg отрапортавал:
0:005> !gcroot 06dc1000

Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info.

Scan Thread 0 OSTHread 1280

ESP:37f2d8:Root:012f6d68(System.Windows.Threading.Dispatcher)->

0130c6b0(System.Windows.Input.InputManager)->
0130cd58(System.Windows.Input.StylusLogic)->
0130ce8c(System.Collections.Generic.Dictionary`2[[System.Object, mscorlib],[System.Windows.Input.PenContexts, PresentationCore]])->
0130ced8(System.Collections.Generic.Dictionary`2+Entry[[System.Object, mscorlib],[System.Windows.Input.PenContexts, PresentationCore]][])->
0135e1e8(System.Windows.Interop.HwndSource)->
012fab4c(TestWpfApp.Window1)->
01334b90(System.Windows.Controls.TextBox)->
0136f664(System.Windows.EffectiveValueEntry[])->
0134deb0(System.Windows.EventHandlersStore)->
01383340(MS.Utility.ThreeObjectMap)->
01383320(MS.Utility.FrugalObjectList`1[[System.Windows.RoutedEventHandlerInfo, PresentationCore]])->
0138332c(MS.Utility.SingleItemList`1[[System.Windows.RoutedEventHandlerInfo, PresentationCore]])->
01383300(System.Windows.Controls.TextChangedEventHandler)->
0137e2d8(TestWpfApp.Window2)->
06dc1000(System.Byte[])

Scan Thread 2 OSTHread 1500

DOMAIN(005656C8):HANDLE(WeakSh):c1794:Root:01384aec(System.EventHandler)->
01384828(System.Windows.Documents.AdornerLayer)->
0137e2d8(TestWpfApp.Window2)
Ту же самую информацию показал мне и CLR memory Profiler.

Другие инструменты

Существует и другие программы для работы с памятью: SciTech’s Memory Profiler, Red-Gate’s ANTS Profiler, YourKit Profiler, JetBrains dotTrace 3.0 и другие. У всех имеется удобный и богатый на функционал пользовательский интерфейс, а также есть улучшенная поддержка, чем у инструментов, приведенных выше.

Другие типы утечек

Существуют и другие типы утечек в управляемом коде, но они выходят за рамки данного поста. Один из типичных случаев утечки, это когда объект находится в неуправляемых ресурсах.

Это может произойти в следующих случаях:

    1) Управляемые объекты храняться в неуправляемых ресурсах и не удаляются из памяти, как обычно это предполагается (как правило, в
IDisposable реализации).

    2) Очень маленький объект, который хранится в большом количестве неуправляемой памяти. Сборщик мусора видит только малое количество управляемой памяти и не понимает, какая конкретно требуется коллекция. Как правило, этот сценарий подходит к точечным (Bitmap) изображениям, так как растровые изображения имеют небольшой управляемый компонент, который отвечает за большой размер неуправляемой памяти. Данное положение улучшилось в .NET 2.0 с введением
AddMemoryPressure API, который позволяет объектам сообщить сборщику мусора размер неуправляемого кластера.

В WPF уже по умолчанию используется сборщик мусора, вследствие чего, рассматривать вопрос об использовании API AddMemoryPressure нужно, только если у вас используется вышеупомянутый сценарий.


Выводы

Во всех выше приведенных моделей, основной проблемой является использование прямых ссылок на объекты, которые остаются в памяти. Некоторые из прямых ссылок, реализованны в WPF Framework'е; однако концепция прямых ссылок, используемых обработчиком события не является новым в WPF и существует с первой версии .NET Framework и WinForms.

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


Ссылки

Пост в блоге Яна Гриффитса: "Simulating Weak Delegates for EventHandler-like Delegates"

Грег Шехтер: Simulating “Weak Delegates” in the CLR 


Блог Rico Mariani: http://blogs.msdn.com/b/ricom/


Инструмент CLR Profiler for the .NET Framework 2.0 

Инструмент WinDbg 

Благодарю Адама Смита (Adam Smith), Эрика Хардинга (Eric Harding) и Момина Аль-Хусейна (Momin Al-Ghosien) - они помогали мне в данном обзоре, а так же Майку Куку (Mike Cook), который участвовал в создании данного поста.

Комментариев нет:

Отправить комментарий


profile for Anatoliy Nikolaev at Stack Overflow, Q&A for professional and enthusiast programmers