Problemas de memory .NET carregando ~ 40 imagens, memory não recuperada, potencialmente devido à fragmentação de LOH

Bem, esta é a minha primeira incursão na memory perfilando um aplicativo .NET (ajuste de CPU que já fiz) e estou atingindo um pouco de espaço aqui.

Eu tenho uma visão no meu aplicativo que carrega 40 imagens (max) por página, cada um executando cerca de 3MB. O número máximo de páginas é 10. Visto que não quero manter 400 imagens ou 1,2 GB de memory de uma só vez, defino cada imagem como nula quando a página é alterada.

Agora, no começo eu pensei que eu deveria ter apenas referências obsoletas para essas imagens. Eu baixei o profiler ANTS (ótima ferramenta BTW) e fiz alguns testes. O gráfico de tempo de vida do object informa que eu não tenho nenhuma referência a essas imagens além da referência única na class pai (que é por design, também confirmada pela meticulosamente vasculhando meu código):

insira a descrição da imagem aqui

A class pai SlideViewModelBase MacroImage para sempre em um cache, mas a propriedade MacroImage é definida como null quando a página é alterada. Não vejo qualquer indicação de que esses objects devam ser mantidos por mais tempo do que o esperado.

A seguir, dei uma olhada no grande heap de objects e no uso da memory em geral. Depois de olhar para três páginas de imagens, tenho 691,9 MB de memory não gerenciada alocada e 442,3 MB no LOH. System.Byte[] , que vem do meu System.Drawing.Bitmap para conversão BitmapImage está tomando praticamente todo o espaço LOH. Aqui está o meu código de conversão:

 public static BitmapSource ToBmpSrc( this Bitmap b ) { var bi = new BitmapImage(); var ms = new MemoryStream(); bi.CacheOption = BitmapCacheOption.OnLoad; b.Save( ms, ImageFormat.Bmp ); ms.Position = 0; bi.BeginInit(); ms.Seek( 0, SeekOrigin.Begin ); bi.StreamSource = ms; bi.EndInit(); return bi; } 

Eu estou tendo dificuldade em encontrar onde toda essa memory não gerenciada está indo. Suspeitei que os objects System.Drawing.Bitmap fossem os primeiros, mas ANTS não os mostra por aí, e também fiz um teste em que eu tinha absoluta certeza de que todos estavam descartados e isso não fazia diferença. Então eu ainda não descobri de onde vem toda essa memory não gerenciada.

Minhas duas teorias atuais são:

  1. Fragmentação LOH. Se eu sair da visualização paginada e clicar em alguns botões, cerca de metade dos ~ 1,5 GB serão recuperados. Ainda muito, mas interessante, no entanto.
  2. Alguma coisa estranha de binding do WPF. Usamos a binding de dados para exibir essas imagens e não sou especialista em relação aos detalhes de como esses controles do WPF funcionam.

Se alguém tem alguma teorias ou dicas de perfil, eu ficaria extremamente grato, pois (é claro) estamos em um prazo apertado e estou lutando um pouco para que essa parte final seja feita e funcione. Acho que fui estragado por rastrear vazamentos de memory em C ++ … quem pensaria?

Se você precisar de mais informações ou quiser que eu tente outra coisa, por favor, pergunte. Desculpe pela parede-o-texto aqui, tentei mantê-la o mais concisa possível.

Esta postagem do blog parece descrever o que você está vendo, e a solução proposta foi criar uma implementação do Stream que envolve outro stream .

O método Dispose dessa class wrapper precisa liberar o stream compactado, para que possa ser coletado como lixo. Depois que o BitmapImage é inicializado com esse stream de wrapper, o stream do wrapper pode ser descartado, liberando o stream subjacente e permitindo que o próprio array de bytes grande seja liberado.

O BitmapImage mantém uma referência ao stream de origem, mantendo o object MemoryStream ativo. Infelizmente, mesmo que MemoryStream.Dispose tenha sido chamado, ele não libera a matriz de bytes que o stream de memory envolve. Portanto, nesse caso, bitmap está referenciando stream, que é o buffer de referência, que pode ocupar muito espaço no heap de object grande. Não há um memory leaks verdadeiro; quando não houver mais referências ao bitmap, todos esses objects serão (eventualmente) coletados como lixo. Mas, como o bitmap já fez sua própria cópia privada da imagem (para renderização), parece um grande desperdício ter a cópia original do bitmap, ainda desnecessária, ainda na memory.

Além disso, qual versão do .NET você está usando? Antes do .NET 3.5 SP1, havia um problema conhecido em que um BitmapImage poderia causar um memory leaks . A solução alternativa era chamar Freeze no BitmapImage.

Onde você está fechando e descartando o stream de memory? Pode ser que o GC tenha que trabalhar muito mais para liberar os resources movendo várias gerações antes de executar os destruidores no object (o que geralmente chamamos de descartar se você esqueceu de fazer isso).

No seu caso, você não pode descartar o stream de memory até terminar a imagem. Quando você quiser que eles sejam descarregados, percorra as imagens e tente descartar o stream de memory.