2012-06-26 146 views
5

我有一个应用程序(WPF)在庞大的数字(如25000)中创建BitmapImages。似乎框架使用了一些内部逻辑,所以在创建之后大约有300MB的内存消耗(150个虚拟和150个物理)。这些BitmapImages被添加到Image对象中,并被添加到Canvas中。问题是,当我释放所有这些图像时,内存没有被释放。我怎样才能释放内存?垃圾回收无法回收BitmapImage?

的应用是简单的: 的Xaml

<Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="*"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition/> 
      <ColumnDefinition/> 
     </Grid.ColumnDefinitions> 
     <Canvas x:Name="canvas" Grid.ColumnSpan="2"></Canvas> 
     <Button Content="Add" Grid.Row="1" Click="Button_Click"/> 
     <Button Content="Remove" Grid.Row="1" Grid.Column="1" Click="Remove_click"/> 
    </Grid> 

代码隐藏

 const int size = 25000; 
     BitmapImage[] bimages = new BitmapImage[size]; 
     private void Button_Click(object sender, RoutedEventArgs e) 
     { 
      var paths = Directory.GetFiles(@"C:\Images", "*.jpg"); 
      for (int i = 0; i < size; i++) 
      { 
       bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length])); 
       var image = new Image(); 
       image.Source = bimages[i]; 
       canvas.Children.Add(image); 
       Canvas.SetLeft(image, i*10); 
       Canvas.SetTop(image, i * 10); 
      } 
     } 

     private void Remove_click(object sender, RoutedEventArgs e) 
     { 
      for (int i = 0; i < size; i++) 
      { 
       bimages[i] = null; 
      } 
      canvas.Children.Clear(); 
      bimages = null; 
      GC.Collect(); 
      GC.Collect(); 
      GC.Collect(); 
     } 

这是ResourceManager中的添加图像后的屏幕截图 enter image description here

+1

“大约300 MB的内存消耗(150个虚拟和150个物理)”完全是BOGUS。阅读内存。 – leppie

+2

请勿使用GC.Collect(),但使用Bitmap.Dispose()。 –

+0

你的电话'GC'不会在那里做任何事情。假设“BitmapImage”对象没有出色的根引用,CLR将在需要时回收内存。 – MoonKnight

回答

1

这仍然是一个数组

BitmapImage[] bimages = new BitmapImage[size]; 

数组是连续的固定长度数据结构,一旦分配给整个数组的内存不能回收它的一部分。尝试使用其它数据结构(如LinkedList<T>)或其他更合适你的情况

+3

这个数组的大小*指针大小,这并没有考虑到被指向的对象所占用的堆空间,根据上面的示例,它们全部被删除并取消引用。所以这个声明虽然准确,但并不能解释为什么在垃圾收集之后内存使用不会改变。 –

+0

@AdamHouldsworth该数组由变量“bimages”引用,存储在本地变量堆栈中。当方法退出时,这个局部变量会超出范围,这意味着什么都不需要引用内存堆中的数组。然后,孤立数组有资格被GC回收。但是,这个集合可能不会立即发生,因为CLR决定是否收集是基于许多因素(可用内存,当前内存分配等)。这意味着在垃圾收集之前所花费的时间有一个不确定的延迟。 – MoonKnight

+0

@Killercam'bimages'不是本地方法,它是一个类成员变量(或者我假设它在上面的两种方法中都使用)。我同意GC是不确定的,但是GC.Collect是确定的 - 那么它只是关于什么应该符合条件的讨论。 –

5

有我们被那里,除非你冻结他们的BitmapImage对象不被咬发布WPF中的错误。 http://blogs.msdn.com/b/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx是我们发现问题的原始页面。它应该在Wpf 3.5 SP1中得到修复,但在某些情况下我们仍然看到它。试着改变你这样的代码,看看如果是这样的问题:

bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length])); 
bimages[i].Freeze(); 

我们经常现在冻结我们的BitmapImage对象,因为我们是在WPF是听有关的BitmapImage事件探查器看到其他实例,从而保持图像活着。

如果Feeze()调用不是你的代码明显的修复,我会强烈建议使用分析,如展鹏内存分析器 - 这将跟踪一个依赖关系树会告诉你它是什么,是保持你的Image对象在内存中。

+5

你有这个bug的任何信息来源吗? –

+0

上述编辑,包括原始地址和Profiler建议如果冻结()不是一个明显的修复。 – fubaar

+1

只是好奇,如果冻结()在方案中调用固定的问题? – fubaar

2

什么工作对我来说是:

  1. 将图像控件的ImageSource的删除包含从UI图像控制之前,空
  2. 运行UpdateLayout请()。
  3. 确保在创建BitmapImage时冻结()BitmapImage,并且没有对用作ImageSources的BitmapImage对象进行非弱引用。

我为每个图像清理方法最终是如此简单:

img.Source = null; 
UpdateLayout(); 

我能够通过实验通过保持一个列表用的WeakReference()对象在每个BitmapImage的指向该到达我创建了它们,然后检查WeakReferences中的IsAlive字段,之后应该对它们进行清理以确认它们实际上已被清理干净。

所以,我的BitmapImage的制作方法是这样的:

var bi = new BitmapImage(); 
using (var fs = new FileStream(pic, FileMode.Open)) 
{ 
    bi.BeginInit(); 
    bi.CacheOption = BitmapCacheOption.OnLoad; 
    bi.StreamSource = fs; 
    bi.EndInit(); 
} 
bi.Freeze(); 
weakreflist.Add(new WeakReference(bi)); 
return bi; 
1

,我只是告诉我关于回收的BitmapImage存储体验。我使用.Net Framework 4.5。
我创建简单的WPF应用程序并加载大图像文件。我尝试使用以下代码从内存中清除映像:

private void ButtonImageRemove_Click(object sender, RoutedEventArgs e) 
    { 

     image1.Source = null; 
     GC.Collect(); 
    } 

但它没有工作。我也试过其他解决方案,但我没有得到我的答案。经过几天的努力,我发现如果我按下按钮两次,GC将释放内存。那么我只需在点击按钮后几秒钟内编写此代码来调用GC收集器。

private void ButtonImageRemove_Click(object sender, RoutedEventArgs e) 
    { 

     image1.Source = null; 
     System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate 
     { 
      System.Threading.Thread.Sleep(500); 
      GC.Collect(); 
     })); 
     thread.Start(); 

    } 

这个代码刚刚在DotNetFr 4.5中测试过。也许你必须冻结BitmapImage对象以降低.Net Framework。
编辑
除非布局得到更新,否则此代码不起作用。我的意思是如果家长控制被删除,GC无法回收它。

2

我遵循AAAA给出的答案。导致内存填满的代码是:

if (overlay != null) overlay.Dispose(); 
overlay = new Bitmap(backDrop); 
Graphics g = Graphics.FromImage(overlay); 

插入AAAA的代​​码块,C#添加“using System.Threading;”和VB增加“进口的System.Threading”:

if (overlay != null) overlay.Dispose(); 
//--------------------------------------------- code given by AAAA 
Thread t = new Thread(new ThreadStart(delegate 
{ 
    Thread.Sleep(500); 
    GC.Collect(); 
})); 
t.Start(); 
//-------------------------------------------- \code given by AAAA 
overlay = new Bitmap(backDrop); 
Graphics g = Graphics.FromImage(overlay); 

重复循环此块现在做一个稳定的,低内存占用。此代码使用Visual Studio 2015社区。

+0

它工作得很好冻结受益。谢谢。 –

+0

此答案适用于WPF或Winforms?我不认为WPF应用程序通常使用(GDI)'Graphics'和'Bitmap'类。 – jrh