Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Ok, I think I might know whats going on here. Take it with a pinch of salt though ... </p> <p>I modified your code slightly. </p> <p><strong>Xaml</strong></p> <pre><code>&lt;Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" x:Name="window" MouseDown="window_MouseDown"&gt; &lt;/Window&gt; </code></pre> <p><strong>Code Behind</strong></p> <pre><code>using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace WpfApplication1 { public partial class MainWindow : Window { private class HeavyObject : UserControl { private byte[] x = new byte[750000000]; public HeavyObject() { for (int i = 0; i &lt; 750000000; i++ ) { unchecked { x[i] = (byte)i; } } // Release optimisation will not compile the array // unless you initialize and use it. Content = "Loadss a memory!! " + x[1] + x[1000]; } } const string msg = " ... nulled the HeavyObject ..."; public MainWindow() { InitializeComponent(); window.Content = msg; } private void window_MouseDown(object sender, MouseEventArgs e) { if (window.Content.Equals(msg)) { window.Content = new HeavyObject(); } else { window.Content = msg; } } } } </code></pre> <p>Now run this up and click on the window. I modified the code to <strong>not use GC.Collect()</strong> and to <strong>set the memory on mousedown</strong>. I am compiling in <strong>Release mode</strong> and running <strong>without a debugger</strong> attached. </p> <p><strong>Click slowly on the window</strong>. </p> <p>The first click you see the memory usage go up by 750MBytes. Subsequent clicks toggle the message between <code>.. Nulled the heavy object..</code> and <code>Loads a memory!</code> <em>so long as you click slowly.</em> There is a slight lag as the memory is allocated and deallocated. The memory usage shouldn't go over 750MBytes but it also doesn't decrease when the heavyobject is nulled. This is by-design as the <a href="http://msdn.microsoft.com/en-us/magazine/cc534993.aspx" rel="noreferrer">GC Large Object Heap is lazy and collects large object memory when new large object memory is needed</a>. From MSDN:</p> <blockquote> <p>If I don't have enough free space to accommodate the large object allocation requests, I will first attempt to acquire more segments from the OS. If that fails, then I will trigger a generation 2 garbage collection in hope of freeing up some space.</p> </blockquote> <p><strong>Click fast on the window</strong></p> <p>Around 20 clicks. What happens? On my PC memory usage does not go up but the mainwindow now does not update the message any more. It freezes. I dont get an out of memory exception and the overall memory usage of my system stays flat. </p> <p><strong>Stress the system using MouseMove on the Window</strong></p> <p>Now replace the MouseDown for a MouseMove event (as per your question code) by changing this Xaml:</p> <pre><code>&lt;Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" x:Name="window" MouseMove="window_MouseDown"&gt; &lt;/Window&gt; </code></pre> <p>I can move the mouse over it for quite a while but <strong>eventually it throws an OutofMemoryException</strong>. A good way to force the exception is do multiple moves then try to maximise the window. </p> <p><strong>The Theory</strong></p> <p>Ok so this is my theory. As your UI thread is running flat out creating and deleting memory inside a message loop event (MouseMove), the Garbage collector is unable to keep up. Garbage collection on the Large Object Heap is done <a href="http://msdn.microsoft.com/en-us/magazine/cc534993.aspx" rel="noreferrer">when memory is needed</a>. It is also an expensive task so performed as infrequently as possible. </p> <p>The lag we noticed when fast clicking on/off/on/off becomes far more pronounced when performed in a MouseMove. If another large object is to be allocated, according to that MSDN article the GC will either try to collect (if memory is not available) or will start using disk. </p> <blockquote> <p>System Is in Low Memory Situation: This happens when I receive the high memory notification from the OS. If I think doing a generation 2 GC will be productive, I will trigger one.</p> </blockquote> <p>Now this part is interesting and I'm guessing here but </p> <blockquote> <p>Finally, as of right now, the LOH is not compacted as a part of collection, but that is an implementation detail that should not be relied on. So to make sure something is not moved by the GC, always pin it. Now take your newfound LOH knowledge and go take control of the heap.</p> </blockquote> <p>So, if the Large Object Heap is not compacted, and you request multiple large objects in a tight loop, <strong>is it possible that collections lead to fragmentation eventually causing an OutOfMemoryException even though there should theoretically be enough memory?</strong></p> <p><strong>The solution</strong></p> <p>Let's free up the UI thread a bit to allow the GC room to breathe. Wrap the allocation code in an asynchronous Dispatcher call and use a low priority, such as SystemIdle. This way it will <em>only</em> allocate the memory when the UI thread is free. </p> <pre><code> private void window_MouseDown(object sender, MouseEventArgs e) { Dispatcher.BeginInvoke((ThreadStart)delegate() { if (window.Content.Equals(msg)) { window.Content = new HeavyObject(); } else { window.Content = msg; } }, DispatcherPriority.ApplicationIdle); } </code></pre> <p>Using this I can dance the mouse pointer all over the form <strong>and it never throws an OutOfMemoryException</strong>. Whether its really working or has "fixed" the problem I don't know but its worth testing. </p> <p><strong>In conclusion</strong></p> <ul> <li>Note that objects of this size are going to be allocated on the Large Object Heap</li> <li>LOH allocations trigger a GC if not enough space is available</li> <li>The LOH is not compacted during a GC cycle so fragmentation may occur, leading to OutOfMemoryException despite there theoretically being enough memory</li> <li>Ideally re-use large objects and don't reallocate. If that is not possible: <ul> <li>Allcate your large objects in a background thread to decouple from the UI thread </li> <li>To allow the GC room to breathe, use the Dispatcher to decouple the allocation until the application is idle</li> </ul></li> </ul>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. COThank you for this elaboration. I reproduced your tests. The "20 Clicks" = Freeze did not occur at my machine. the Window stayed unchanged until all Clicks were handled (took a while) and then returned to update normally. Maybe you did not wait long enough? Memory behavior is the same here. Maximizing the window under stress seems to do exactly the same as leaving and re-entering the window with the mouse cursor (as in the original question). Maybe the leave and enter events allocate memory so the next HeavyObject does not fit into the gap of the old one anymore?
      singulars
    2. CO@die_hoernse to force the software to keep it slow, you could use the Dispatcher with low priority to decouple the Click event from the DoBigMemoryOperation function and prevent re-entrancy until the BigMemoryOperation has returned. Doing your BigMemoryOperation in a background thread also allows reporting of progress and graying out the UI/disabling buttons while working - tricks like this can really help to prevent production crashes when stressing the GC
      singulars
    3. COIn "stress" mode, when there is twice (or more - made it 250 instead of 750MB) the memory of HeavyObject allocated and the cursor is outside the window, it stays allocated until the cursor re-enters. My theory is, GC is idle due to inactivity of the program and no system memory pressure. Given that there is no leak but the LOH will eventually be compacted I consider your last conclusion the solution: To keep it in a background thread. However, if I shall discover that memory stays allocated forever, even under memory pressure without mentionable I/O or CPU load, this question will be updated.
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload