2012-06-26 177 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