내용 보기
작성자
관리자 (IP : 172.17.0.1)
날짜
2020-09-25 03:24
제목
[WPF] WPF의 메모리 릭 발생 가능성 with dotMemory
Fighting Common WPF Memory Leaks with dotMemoryPosted on by When developing Windows Presentation Foundation (WPF), Silverlight and Windows Store applications, a number of common memory leaks may surface. In this blog post, we’ll see how dotMemory can help detect these common WPF memory leaks and how we can fix them. Here are the leaks we will discuss in this post:
Binding leakWPF comes with a number of data binding patterns, which, if we break them, can cause memory leaks in our applications. Take the following class: When we bind to an instance’s Name property, the binding target starts listening for property change notifications. If the property is not a DependencyProperty or an object that implements INotifyPropertyChanged, WPF will resort to subscribing to the ValueChanged event of the System.ComponentModel.PropertyDescriptor class to get notifications when the source object’s property value changes. Why is this a problem? Well, since the runtime creates a reference to this PropertyDescriptor, which in turn references our source object, and the runtime will never know when to deallocate that initial reference (unless explicitly told), both the PropertyDescriptor as well as our source object will remain in memory. How to detect it?dotMemory comes with a series of automatic inspections, detecting common memory leaks. When profiling an application, we can instantiate the control which binds to our class and then dispose that control. When opening a snapshot in dotMemory, the snapshot overview page will tell us about WPF binding leaks immediately. This should be all we need to know, but let’s see if we can find proof of the theory above about the PropertyDescrriptor’s ValueChanged event handler keeping our objects in memory. After double-clicking the list entry, we can see the object set open. When we navigate to the Group by Similar Retention view, we get proof from the ValueChangedEventManager retaining our object: How to fix it?The simplest fix for a WPF binding leak would be making our Name property a DependencyProperty, or implementing the INotifyPropertyChanged interface correctly on our Person class and its Name property, like so: If the object is of a type we can not edit, for example because it comes from a library we’re depending on, we can also explicitly clear the binding by calling: Note that if a binding has the OneTime mode, this leak will not be present as the binding is done only once and the binding target will not listen for changes on the source object. Collection binding leakA similar issue to the WPF binding leak is the Collection binding leak. If there is binding to a collection that does not implement the INotifyCollectionChanged interface, WPF creates a strong reference to this collection. As a result, it stays in memory for the entire application lifetime. How to detect it?We will have to run our application and bind a control like a ListBox, for example, to our collection. Next, we can remove the binding by destroying the control and then taking a snapshot using dotMemory. We would expect our collection to be removed from memory, yet if we look at the snapshot overview we can see a WPF collection binding leak had been detected. If we then open the object set and look at the Group by Dominators view, we can see our collection is held in memory by the WPF DataBindEngine, an object which will be around for the lifetime of our application. And as long as our object dominator stays in memory, our collection will, too… How to fix it?An easy way of fixing this issue is by implementing the INotifyCollectionChanged interface on our custom collection type. If the collection does not need any specific implementations, we could also inherit from the ObservableCollection type as it handles the implementation for us. Textbox undo leakOne of the great things about WPF is that it enables Undo on several controls, like a textbox for example. For every change we make to its contents, WPF will keep the actions in memory so we can easily undo them using Ctrl+Z. Now imagine our application has a textbox in which lots of changes are being done… By default, the WPF UndoManager will keep up to 100 of these actions and in earlier WPF versions this limit was not there. While not really a memory leak as such, having a high undo limit on textboxes in our applications may cause excessive memory usage without us evening knowing. And if we profile the application, we will see this as well. How to detect it?After running our application and making a large number of changes to a textbox’ contents, the dotMemory snapshot overview could show a large number of Char[] objects. If we drill deeper into this object set and look at the dominators (Group by Dominators), we can see that these object types are held in memory by several others. The first dominator here (TextTreeRootNode) is our textbox control itself. Of course it needs a few Char[] arrays to hold its contents. The second one however, UndoManager, is more interesting. It seems the UndoManager is keeping on to quite a few Char[] arrays as well. Logical, as WPF’s undo behavior will need this information to be able to undo/redo changes made to the textbox. How to fix it?First of all, this is not really a leak. It’s a feature! It is important to know it’s there, though, for two reasons. The first one is when profiling WPF applications, we may see a number of Char[] arrays being created. Don’t get distracted by the UndoManager and try focusing on other dominators if the allocations are too excessive. Second, when building applications where a lot of text editing is done, high memory usage can be explained by this undo behavior. To limit the number of entries the undo and redo stacks can hold, we can update the textbox’ UndoLimit property to a lower number. WPF used to default to –1 (unlimited) but in recent versions it defaults to 100. We could also turn off undo entirely, by changing the IsUndoEnabled property. Event Handler leakA common leak, not only in WPF but also in other frameworks, is the event handler leak. Event handler leaks are caused by developer oversight. Here’s an example. Imagine we open an AdWindow window in our application, and let it update its contents every few seconds. We could instantiate a DispatcherTimer in our constructor and subscript to the Tick event to handle these updates. Now what happens if we close this AdWindow? It all depends… If we do nothing, the DispatcherTimer will keep on firing Tick events, and since we’re still subscribed to it, the ChangeAds event handler will be called. And if that event handler has to remain in memory for it to be called, our AdWindow will stay in memory too, even if we expected it to be released. How to detect it?There are a number of ways to detect this type of leak. The easiest is to capture a snapshot after the object was expected to be released. In the snapshot overview page, we will immediately see if the object remained in memory because of an event handler leak. See our AdWindow there? dotMemory detected it is kept in memory because of an event handler leak. Now how to find out which event handler is keeping it in memory… If we double-click the entry, we will see the details of the instance. Under Key Retention Paths, we can easily identify which type’s event handler retains our object in memory: the DispatcherTimer. If we know our code base,we know where to look. But imagine this is the first time we see the code base, how do we know where we are subscribing to this event handler? First of all, we want to make sure our snapshot was captured collecting creation stack traces (can be enabled in the profiler options). From the Key Retention Paths diagram, we can now double-click the EventHandler entry here, which will open the specific event handler instance. From the Creation Stack Trace view, we can see we’re subscribing the event handler in the AdWindow constructor. The Shortest Paths to Roots Tree view will tell us which event we’re subscribing to. How to fix it?From the investigation above, we know which event and which event handler we’ve forgotten to unsubscribe from (DispatcherTimer’s Tick event), and where we’re subscribing to it in the first place (the AdWindow constructor). Unsubscribing from the event in the constructor is pointless in this case, as it would render our functionality of rotating content every few seconds useless. A more logical place to unsubscribe is when closing the AdWindow: Note: The DispatcherTimer example here is a special case, as the above will still not ensure our AdWindow is released from memory. If we profile the application, we’d be able to see the AdWindow instance is still there. The Key Retention Paths diagram will help discover we have to set the private variable adTimer to null as well, to remove another reference from the .NET runtime’s DispatcherTimers collection. Or how one memory leak can hide another. x:Name leakThe beauty of building UI’s in software is that we can do some nice things with it, like removing controls from the UI when a given action is performed. Depending on how we build our UI, we may be introducing a memory leak by doing this… WPF creates a strong global reference to the UI element that is declared in XAML if it uses the x:Name directive. Dynamically removing an element from code will not remove the control from memory… Not even if we remove it from the parent control’s Children collection. How to detect it?After clicking he button that removes our control, we can capture a snapshot in dotMemory. The snapshot overview has an automatic inspection for x:Name leaks and will tell us our control remained in memory. We can drill down and look at the Key Retention Paths to see WPF is retaining our object in memory. How to fix it?To ensure the control gets removed from memory, we will have to call the UnregisterName method of the parent control. The code that removes our control from the window could be updated to look like this: There are many more common memory leaks and things we can do to improve our applications. Check the Unusual Ways of Boosting up App Performance series we did earlier. New automatic inspections on WPF collection binding and WPF x:Name were released with the latest v.4.0.10 update on September 2, 2014. Please note: To use dotMemory 4.0.10, your subscription should be valid until September 2, 2014. Otherwise, the subscription renewal is required. |
출처1
https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
출처2