Este artigo foi traduzido por máquina.

C#, Visual Basic e C++

Gerenciando a memória em aplicativos da Windows Store - Parte 2

Rua Chipalo
Dan Taylor

 

No Windows 8 edição especial da MSDN Magazine, o primeiro artigo desta série discutido como vazamento de memória ocorre, porque eles abrandar o seu app e degradam a experiência geral do sistema, formas gerais para evitar vazamentos e questões específicas que foram encontrados para ser problemático em JavaScript apps (consulte "Gerenciamento de memória no Windows Apps da loja," msdn.microsoft.com/magazine/jj651575). Agora vamos olhar para vazamentos de memória no contexto de aplicativos c#, Visual Basic e C++. Vamos analisar algumas maneiras básicas vazamentos ocorreram em gerações passadas de apps e como Windows 8 tecnologias ajudam a evitar essas situações. Com essa base, vamos movê cenários mais complexos que podem causar o seu app para vazamento de memória. Vamos a ele!

Ciclos simples

No passado, muitos vazamentos foram causados por ciclos de referência. Objetos envolvidos no ciclo teria sempre uma referência ativa mesmo se os objetos no ciclo nunca poderiam ser alcançados. A referência ativa seria manter os objetos vivo para sempre, e se um programa criado com freqüência esses ciclos, continuaria a perda de memória ao longo do tempo.

Um ciclo de referência pode ocorrer por vários motivos. O mais óbvio é quando objetos explicitamente referência uns aos outros. Por exemplo, o código a seguir resulta em imagens em Figura 1:

Foo a = new Foo();
Bar b = new Bar();
a.barVar = b;
b.fooVar = a;


Figura 1 referência Circular

Felizmente, em coleta de lixo linguagens como c#, JavaScript e Visual Basic, este tipo de referência circular vai ser automaticamente limpa uma vez que as variáveis não são mais necessários.

C + + CX, em contraste, não utiliza a coleta de lixo. Em vez disso, baseia-se em contagens de referência para realizar o gerenciamento de memória. Isso significa que os objetos ser recuperados pelo sistema apenas quando tiverem zero referências ativas. Nessas línguas, os ciclos entre esses objetos forçaria A e B para viver para sempre, porque eles nunca teria zero referências. Pior ainda, tudo referenciado pela e B viveria para sempre também. Este é um exemplo simplificado que pode ser facilmente evitado quando se escreve programas básicos; no entanto, programas complexos podem criar ciclos que envolvem vários objetos encadeando de formas não-óbvias. Vamos dar uma olhada em alguns exemplos.

Ciclos com manipuladores de eventos

Como discutido no artigo anterior, os manipuladores de eventos são uma maneira extremamente comum para referências circulares a ser criado. Figura 2 mostra como isso pode ocorrer.

Figura 2, causando uma referência Circular com um manipulador de evento

<MainPage x:Class="App.MainPage" ...>
  ...
<TextBlock x:Name="displayTextBlock" ...
/>
  <Button x:Name="myButton" Click="ButtonClick" ...
/>
  ...
</MainPage>
public sealed partial class MainPage : Page
{
  ...
private void ButtonClick(object sender, RoutedEventArgs e)
  {
    DateTime currentTime = DateTime.Now;
    this.displayTextBlock.Text = currentTime.ToString();
  }
  ...
}

Aqui nós simplesmente adicionamos um botão e um TextBlock para uma página. Podemos também configurar um manipulador de evento definido na classe Page, para evento de Click do botão. Esse manipulador atualiza o texto de TextBlock para mostrar a hora atual sempre que o botão é clicado. Como você verá, até mesmo esse exemplo simples tem uma referência circular.

O botão e TextBlock são filhos da página e, portanto, a página deve ter uma referência para eles, como mostrado no diagrama superior Figura 3.


Figura 3 referência Circular relacionada ao manipulador de evento

No diagrama inferior, outra referência é criada pelo registro do manipulador de eventos, que é definido na classe Page.

A origem do evento (botão) tem uma forte referência ao manipulador de evento, um método, para que a fonte pode chamar o manipulador de eventos quando o evento é acionado. Vamos chamar esse delegado um representante forte porque a referência dele é forte.

Agora temos uma referência circular. Uma vez que o usuário navega fora da página, o coletor de lixo (GC) é inteligente o suficiente para recuperar o ciclo entre a página e um botão. Esses tipos de referências circulares serão automaticamente limpos quando eles já não são necessários se você estiver escrevendo aplicativos em JavaScript, c# ou Visual Basic. Como observamos anteriormente, no entanto, C + + CX é uma língua ref-contado, que significa que objetos são automaticamente excluídos somente quando sua contagem de referência cai a zero. Aqui, as fortes referências criadas forçaria a página e botão para viver para sempre, porque eles nunca teria zero referência contagens. Pior ainda, todos os itens contidos pela página (potencialmente um grande elemento de árvore) viveria para sempre bem porque a página contém referências a todos esses objetos.

Naturalmente, criar handers evento é um cenário extremamente comum e Microsoft não quer isso para causar vazamentos em seu aplicativo, independentemente do idioma que você usar. Por que razão o compilador XAML faz referência do delegado para o ouvinte de evento uma referência fraca. Você pode pensar nisso como um delegado fraco porque a referência do delegado é uma referência fraca.

O delegado fraco garante que a página não é mantida viva por referência do delegado para a página. A referência fraca não contam contra o contagem de referência da página e assim lhe permitirá ser destruída uma vez que todas as outras referências cair para zero. Posteriormente, o botão, TextBlock e nada mais referenciado pela página serão destruídos também.

Fontes de eventos de longa duração

Às vezes um objeto com uma vida longa define eventos. Nos referimos a esses eventos como Long-lived, porque os eventos compartilham o tempo de vida do objeto que define. Esses eventos de longa duração mantiverem referências a todos os manipuladores registrados. Isso força os manipuladores e os objetos visados pelos manipuladores, para se manter vivo, contanto que a fonte de evento de longa duração.

Na seção "Manipulador" do artigo anterior de vazamento de memória, analisamos um exemplo deste. Cada página em um aplicativo registra-se para SizeChangedEvent a janela aplicativo. A referência SizeChangedEvent da janela para o manipulador de eventos na página manterá cada instância de página viva, enquanto a janela do aplicativo é ao redor. Todas as páginas que navegou para permanecer vivo mesmo que apenas um deles está em exibição. Este vazamento é facilmente corrigido por cancelar o registro de manipulador de SizeChangedEvent da cada página quando o usuário navega fora da página.

Nesse exemplo, é claro quando a página não é mais necessário e o desenvolvedor é capaz de cancelar o registro do manipulador de eventos a partir da página. Infelizmente não é sempre fácil de razão sobre a vida de um objeto. Considere simulando um representante"fraco" em c# ou Visual Basic, se você localizar vazamentos causados por eventos de longa duração segurando a objetos através de manipuladores de eventos. (Consulte "Simulando 'Delegados fracos' no CLR" no bit.ly/SUqw72.) O delegado fraco padrão coloca um objeto intermediário entre a fonte de evento e o manipulador de eventos. Use uma referência forte da fonte de evento para o objeto intermediário e uma referência fraca do objeto intermediário para o manipulador de eventos, conforme mostrado no Figura 4.


Figura 4 usando um objeto intermediário entre a fonte de evento e o ouvinte de evento

No diagrama superior Figura 4, LongLivedObject expõe Travel e ShortLivedObject registra EventAHandler para manipular o evento. LongLivedObject tem um tempo de vida muito maior do que a curta duração­objeto e a forte referência entre EventA e EventAHandler irão manter ShortLivedObject vivo, enquanto LongLivedObject. Colocar um IntermediateObject entre LongLivedObject e ShortLivedObject (como mostrado no diagrama inferior) permite IntermediateObject vazamento em vez de ShortLivedObject. Este é um vazamento muito menor, porque o IntermediateObject precisa expor apenas uma função, enquanto ShortLivedObject podem conter estruturas de dados grandes ou uma árvore visual do complexa.

Vamos dar uma olhada como um delegado fraco poderia ser implementado no código. Um evento de muitas classes podem querer registrar é exibir­Properties.OrientationChanged. DisplayProperties é realmente uma classe estática, assim o OrientationChanged evento será ao redor para sempre. O evento vai realizar uma referência a cada objeto que você usa para ouvir o evento. No exemplo descrito no Figura 5 e Figura 6, a classe LargeClass usa o padrão fraco delegado para garantir que o evento OrientationChanged mantém uma forte referência somente a uma classe intermediária quando um manipulador de evento é registrado. A classe intermediária chama o método, definido em LargeClass, o que realmente faz o trabalho necessário quando o OrientationChanged evento é acionado.


Figura 5 o fraco padrão de delegado

Figura 6 implementando um delegado fraco

public class LargeClass
{
  public LargeClass()
  {
    // Create the intermediate object
    WeakDelegateWrapper wrapper = new WeakDelegateWrapper(this);
    // Register the handler on the intermediate with
    // DisplayProperties.OrientationChanged instead of
    // the handler on LargeClass
    Windows.Graphics.Display.DisplayProperties.OrientationChanged +=
      wrapper.WeakOrientationChangedHandler;
  }
  void OrientationChangedHandler(object sender)
  {
    // Do some stuff
  }
  class WeakDelegateWrapper : WeakReference<LargeClass>
  {
    DisplayPropertiesEventHandler wrappedHandler;
    public WeakDelegateWrapper(LargeClass wrappedObject,
      DisplayPropertiesEventHandler handler) : base(wrappedObject)
    {
      wrappedHandler = handler;
      wrappedHandler += WeakOrientationChangedHandler;
    }
    public void WeakOrientationChangedHandler(object sender)
    {
      LargeClass wrappedObject = Target;
      // Call the real event handler on LargeClass if it still exists
      // and has not been garbage collected.
Remove the event handler
      // if LargeClass has been garbage collected so that the weak
      // delegate no longer leaks
      if(wrappedObject != null)
        wrappedObject.OrientationChangedHandler(sender);  
      else
        wrappedHandler -= WeakOrientationChangedHandler;
    }
  }
}

Lambdas

Muitas pessoas acham mais fácil implementar manipuladores de eventos com um lambda — ou função inline, em vez de um método. Vamos converter o exemplo da Figura 2 para fazer exatamente isso (ver Figura 7).

Figura 7 implementar um manipulador de eventos com um Lambda

<MainPage x:Class="App.MainPage" ...>
  ...
<TextBlock x:Name="displayTextBlock" ...
/>
  <Button x:Name="myButton" ...
/>
  ...
</MainPage>
public sealed partial class MainPage : Page
{
  ...
protected override void OnNavigatedTo
  {
    myButton.Click += => (source, e)
    {
      DateTime currentTime = DateTime.Now;
      this.displayTextBlock.Text = currentTime.ToString();
    }
  ...
}

Usando um lambda também cria um ciclo. As primeiras referências são ainda, obviamente, criadas a partir da página para o botão e o TextBlock (como o diagrama superior em Figura 3).

O próximo conjunto de referências, ilustrado na Figura 8, invisivelmente é criado por lambda. Evento de Click do botão é ligado a um objeto RoutedEventHandler cujo método Invoke é implementado por um fechamento em um objeto interno criado pelo compilador. O encerramento deve conter referências a todas as variáveis referenciadas por lambda. Uma dessas variáveis é "este", que, no contexto do lambda — refere-se à página, criando assim o ciclo.


Figura 8 referências criadas por Lambda

Se o lambda é escrito em c# ou Visual Basic, o GC do CLR vai recuperar recursos envolvidos neste ciclo. No entanto, em C + + CX esse tipo de referência é uma referência forte e irá causar um vazamento. Isso não significa que todos os lambdas em C + + vazamento CX. Uma referência circular não ter sido criada se não tinha referenciado "isso" e usado somente variáveis locais para o encerramento, ao definir o lambda. Como uma solução para este problema, se você precisar acessar uma variável externa ao fechamento em um manipulador embutido, implemente esse manipulador de eventos como um método em vez disso. Isso permite que o compilador XAML criar uma referência fraca do evento para o manipulador de eventos e a memória vai ser recuperada. Outra opção é usar a sintaxe de ponteiro para membro, o que permite que você especifique se uma referência forte ou fraca é tomada contra a classe que contém o método de ponteiro para membro (no caso, a página).

Use o parâmetro do remetente evento

Como discutido no artigo anterior, cada manipulador de evento recebe um parâmetro, chamado geralmente "remetente", que representa a fonte de evento. O parâmetro de fonte de evento de um lambda ajuda evitar referências circulares. Vamos modificar nosso exemplo (usando C + + CX) então o botão mostra o tempo atual, quando ele for clicado (ver Figura 9).

Figura 9 fazendo o botão Mostrar a hora atual

<MainPage x:Class="App.MainPage" ...>
  ...
<Button x:Name="myButton" ...
/>
  ...
</MainPage>
MainPage::MainPage()
{
   ...
myButton->Click += ref new RoutedEventHandler(
     [this](Platform::Object^ sender, 
     Windows::UI::Xaml::RoutedEventArgs^ e)
   {    
     Calendar^ cal = ref new Calendar();
     cal->SetToNow() ;
     this->myButton->Content = cal->SecondAsString();
   });
   ...
}

O lambda atualizado cria as mesmas referências circulares, ilustradas na Figura 8. Eles vão causar C + + CX de vazamento, mas isso pode ser evitado usando o parâmetro de origem em vez de referenciar myButton através da variável de "this". Quando o método de fechamento é executado, ele cria os parâmetros de "fonte" e "e" na pilha. Essas variáveis vivem somente para a duração da chamada do método, em vez de para, enquanto o lambda está conectado ao manipulador de eventos do botão (currentTime tem a mesma vida útil). Aqui está o código para usar o parâmetro de fonte:

MainPage::MainPage()
{
  ...
myButton->Click += ref new RoutedEventHandler([](Platform::Object^ sender,
  Windows::UI::Xaml::RoutedEventArgs^ e)
  {    
    DateTime currentTime ;
    Calendar^ cal = ref new Calendar();
    cal->SetToNow() ;
    Button ^btn = (Button^)sender ;
    btn->Content = cal->SecondAsString();  });
  ...
}

As referências agora olham como o que é mostrado no Figura 10. A referência representada em vermelho, criando o ciclo, está presente apenas durante a execução do manipulador de eventos. Esta referência é destruída depois que o manipulador de eventos for concluída e ficamos com nenhum ciclo que irá causar um vazamento.


Figura 10 usando o parâmetro de origem

Use WRL para evitar vazamentos no código C++ padrão

Você pode usar o padrão C++ para criar aplicativos Windows Store, além de JavaScript, c#, C + + CX e Visual Basic. Ao fazer isso, familiarizados COM técnicas são empregados, como referência de contagem gerenciar o tempo de vida de objetos e valores HRESULT para determinar se uma operação de êxito ou falha de teste. O Windows Runtime C++ modelo biblioteca (WRL) simplifica o processo de escrever esse código (bit.ly/P1rZrd). Recomendamos que você usá-lo quando implementar o padrão C++ Windows Store apps para reduzir bugs e vazamentos de memória, que podem ser extremamente difícil de localizar e resolver.

Usar manipuladores de eventos que atravessam fronteiras de linguagem com cuidado

Finalmente, há um padrão de codificação que requer uma atenção especial. Nós discutimos a possibilidade de vazamentos de referências circulares que envolvem manipuladores de eventos, e que muitos destes casos podem ser detectados e evitados por atenuações plataforma fornecida. Estas reduções não se aplicam quando o ciclo atravessa vários montes de coleta de lixo.

Vamos dar uma olhada em como isso pode acontecer, como mostrado na Figura 11.

Figura 11 exibindo a localização do usuário

<Page x:Class="App.MainPage" ...>
  ...
<TextBlock x:Name="displayTextBlock" ...
/>
  ...
</Page>
public sealed partial class MyPage : Page
{
  ...
Geolocator gl;
  protected override void OnNavigatedTo{} ()
  {
    Geolocator gl = new Geolocator();
    gl.PositionChanged += UpdatePosition;
  }
  private void UpdatePosition(object sender, RoutedEventArgs e)
  {
    // Change the text of the TextBlock to reflect the current position
  }
  ...
}

Este exemplo é muito semelhante dos exemplos anteriores. Uma página contém um TextBlock que exibe um pouco de informação. Neste exemplo, porém, o TextBlock exibe a localização do usuário, como mostrado na Figura 12.


Figura 12 referências circulares abrangem um limite de coletor de lixo

Neste ponto, você provavelmente poderia desenhei as referências circulares-se. O que não é óbvio, no entanto, é que as referências circulares abrangem um limite de coletor de lixo. Porque as referências estendem fora do CLR, o CLR GC não pode detectar a presença de um ciclo e isso irá vazar. É difícil evitar estes tipos de vazamentos, porque você não pode sempre dizer em que língua um objeto e seus eventos são implementados. Se Geolocator é escrito em c# ou Visual Basic, as referências circulares vão ficar dentro do CLR e o ciclo será a coleta de lixo. Se a classe é escrita em C++ (como neste caso) ou JavaScript, o ciclo irá causar um vazamento.

Existem algumas maneiras para garantir que seu aplicativo não é afetado por vazamentos como este. Primeiro, você não precisa se preocupar com esses vazamentos, se você estiver escrevendo um aplicativo de JavaScript puro. O GC JavaScript muitas vezes é inteligente o suficiente para rastrear referências circulares em todos os objetos do WinRT. (Consulte o artigo anterior para mais detalhes sobre o gerenciamento de memória JavaScript.)

Você também não precisa se preocupar se você estiver registrando para eventos em objetos, que você sabe que estão no âmbito XAML. Isso significa que qualquer coisa no namespace Windows.UI.Xaml e inclui todas as classes FrameworkElement, UIElement e controle familiares. O GC do CLR é inteligente o suficiente para rastrear referências circulares através de objetos XAML.

A outra maneira de lidar com este tipo de vazamento é cancelar o registro do manipulador de eventos quando não é mais necessária. Neste exemplo, você pode cancelar o registro o manipulador de evento OnNavigatedFrom. Referência criada pelo manipulador seria removida e todos os objetos que são destruídos. Note que não é possível cancelar o registro lambdas, tão manipulando um evento com um lambda pode causar vazamentos.

Análise de vazamentos de memória em aplicativos de armazenamento do Windows usando c# e Visual Basic

Se você estiver escrevendo uma janela loja app em c# ou Visual Basic, é útil observar que muitas das técnicas discutiram na artigo anterior em JavaScript aplicar também para c# e Visual Basic. Em particular, o uso de referências fracas é uma forma comum e eficaz para reduzir o crescimento da memória (ver bit.ly/S9gVZW para obter mais informações), e os padrões de arquitetura de "Descarte" e "Inchar" aplicam-se igualmente bem.

Agora vamos tomar uma olhada em como você pode encontrar e corrigir comum vazamentos usando ferramentas disponíveis hoje: Gerenciador de tarefas do Windows e um perfil de código gerenciado ferramenta chamada PerfView, disponível para download em bit.ly/UTdb4M.

Na seção "Manipuladores" o artigo anterior sobre vazamentos, vimos um exemplo chamado LeakyApp (repetidas em Figura 13 para sua conveniência), que faz com que um vazamento de memória no manipulador de evento SizeChanged da sua janela.

Figura 13 LeakyApp

public sealed partial class ItemDetailPage : LeakyApp.Common.LayoutAwarePage
  {
    public ItemDetailPage()
    {
      this.InitializeComponent();
    }
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);
      Window.Current.SizeChanged += WindowSizeChanged;
    }
    private void WindowSizeChanged(object sender,
      Windows.UI.Core.WindowSizeChangedEventArgs e)
    {
      // Respond to size change
    }
// Other code
  }

Em nossa experiência, este é o tipo mais comum de vazamento em c# e Visual Basic código, mas as técnicas que iremos descrever assim como aplicam evento circular vazamentos e crescimento ilimitado de estrutura de dados. Vamos dar uma olhada em como você pode encontrar e corrigir vazamentos em seus próprios aplicativos usando ferramentas disponíveis hoje.

Olhando para o crescimento de memória

O primeiro passo para corrigir um vazamento de memória é identificar o crescimento constante da memória das operações que devem ser neutra de memória. Na seção "Descobrindo vazamentos de memória" o artigo anterior, discutimos uma maneira muito simples, você pode usar o Gerenciador de tarefas do Windows built-in para observar o crescimento do Total de trabalho conjunto (TWS) de um app rodando através de um cenário várias vezes. No aplicativo de exemplo, as etapas para causar um vazamento de memória são para clicar em um azulejo e, em seguida, navegar de volta para a página inicial.

Em Figura 14, a imagem superior mostra o conjunto de trabalho no Gerenciador de tarefas antes de 10 iterações dessas etapas e a imagem de fundo mostra isso após 10 iterações.



Figura 14 assistindo para crescimento de memória

Depois de 10 iterações, você pode ver a quantidade de memória usada tem crescido de 44.404 K para 108, 644 K. Isto definitivamente parece um vazamento de memória, e nós deve cavar ainda mais.

Adicionando o determinismo de GC

Para ter certeza de que temos um vazamento de memória em nossas mãos, temos de confirmar que persiste após a limpeza de coleta de lixo completa. O GC usa um conjunto de heurísticas para decidir a melhor hora para executar e recuperar memória morta, e ele geralmente faz um bom trabalho. No entanto, a qualquer momento pode haver um número de "mortos" objetos na memória que ainda não foram recolhidos. Deterministicamente chamar o GC permite separar crescimento causado pela coleção lenta e crescimento causados por vazamentos de verdadeiros, e ele limpa a imagem, quando olhamos para investigar quais objetos estão verdadeiramente vazando.

A maneira mais fácil de fazer isso é usar o botão de "Força GC" de dentro PerfView, mostrado na próxima seção as instruções em tirar um instantâneo de heap. Outra opção é adicionar um botão para seu aplicativo que irá disparar os GCs usando código. O código a seguir irá induzir uma coleta de lixo:

private void GCButton_Click(object sender, RoutedEventArgs e)
{
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();
}

WaitForPendingFinalizers e subsequente chamada Collect certifique-se de que quaisquer objetos liberados como resultado de finalizadores irão obter recolhidos também.

No aplicativo de exemplo, no entanto, este botão após 10 iterações libertadas apenas 7 MB de 108 MB de conjunto de trabalho. Neste ponto pode ser certeza que há um vazamento de memória no LeakyApp. Agora, precisamos olhar para a causa do vazamento de memória em nosso código gerenciado.

Analisando o crescimento da memória

Agora vamos usar o PerfView para tirar um diff de Heap de GC do CLR e analisar o diff para encontrar os objetos vazou.

Para descobrir onde a memória é ser vazada, você vai querer tirar um instantâneo da pilha antes e após a execução através de um vazamento -­fazendo com que a ação em seu aplicativo. Usando PerfView, você pode diff dois instantâneos para encontrar onde está o crescimento de memória.

Para tirar um instantâneo de heap com PerfView:

  1. PerfView aberto.
  2. Clique em memória na barra de menu.
  3. Clique em tomar instantâneo de Heap (ver Figura 15).
  4. Selecione sua app Store do Windows na lista.
  5. Clique no botão "Force GC" para induzir um GC dentro de seu aplicativo.
  6. Definir o nome do arquivo de despejo que você deseja salvar e clique em despejo Heap de GC (ver Figura 16).


Figura 15 tirar um instantâneo de Heap


Figura 16 o Heap de GC de Dumping

Um despejo de pilha gerenciada será salva o arquivo especificado e PerfView vai abrir uma tela de arquivo de despejo, mostrando uma lista de todos os tipos no heap gerenciado. Uma investigação de vazamento de memória, você deve excluir o conteúdo da dobra % e FoldPats caixas de texto e clique no botão atualizar. Na visualização resultante, a coluna Exc mostra o tamanho total em bytes que tipo está usando no heap GC e a coluna Exc Ct mostra o número de instâncias do mesmo tipo no heap GC.

Figura 17 mostra uma vista do despejo de GC para LeakyApp.


Figura 17 Heap instantâneo em PerfView

Para obter uma comparação de dois instantâneos de heap mostrando o vazamento de memória:

  1. Executado através de algumas iterações da ação que faz com que o vazamento de memória em seu aplicativo. Isso inclui quaisquer objetos inicializados preguiçoso carregado ou one-time em sua linha de base.
  2. Tirar um instantâneo de heap, incluindo forçando um GC para remover quaisquer objetos mortos. Vou referimos a isto como o instantâneo "antes".
  3. Executado através de várias iterações mais do seu vazamento -­causando a ação.
  4. Instantâneo outra pilha, incluindo forçando um GC para remover quaisquer objetos mortos. Este será o "depois" instantâneo.
  5. A vista do instantâneo depois, clique o item de menu ' diff ' e selecione o antes instantâneo como sua linha de base. Certifique-se de ter aberto a partir de seu ponto de vista o antes instantâneo, ou ele não vai aparecer no menu do diff.
  6. Será mostrada uma nova janela contendo o diff. Exclua o conteúdo da dobra % e caixas de texto FoldPats e atualizar a exibição.

Agora você tem uma vista que mostra o crescimento em objetos gerenciados entre os dois instantâneos de sua pilha gerenciada. Para LeakyApp, pegamos o antes instantâneo após três iterações e o instantâneo após após 13 iterações, dando a diferença no heap de GC após 10 iterações. O diff instantâneo de pilha de PerfView é mostrado na Figura 18.


Figura 18 a comparação de duas fotos em PerfView

A coluna Exc dá o aumento no tamanho total de cada tipo no heap gerenciado. No entanto, a coluna Exc Ct vai mostrar a soma das instâncias instantâneos duas pilha, em vez da diferença entre os dois. Isto é não o que você esperaria para este tipo de análise, e futuras versões do PerfView permitirá que você ver esta coluna como uma diferença; por agora, apenas ignore a coluna Exc Ct quando usando o modo de exibição de comparação.

Todos os tipos que vazaram entre os dois instantâneos terá um valor positivo na coluna Exc, mas determinar qual objeto está impedindo os objetos sendo coletados terá algumas análises.

Analisando o Diff

Com base em seus conhecimentos sobre o app, você deve olhar para a lista de objetos em diff e encontrar todos os tipos que você não esperaria crescer ao longo do tempo. Olhar para tipos que são definidos em seu aplicativo em primeiro lugar, porque um vazamento é provável ser o resultado de uma referência que está sendo realizada pelo seu código de aplicativo. O próximo lugar a olhar é vazada tipos no namespace Windows.UI.Xaml, como estes são propensos a ser realizada sobre a pelo seu código de aplicativo também. Se olharmos primeiros em tipos definidos apenas em nosso aplicativo, o tipo de ItemDetailPage aparece no topo da lista. É o maior objeto vazado definido em nosso aplicativo de exemplo.

Clicando duas vezes em um tipo na lista irão levá-lo para o "Refered-de" (sic) vista para esse tipo. Esta vista mostra uma árvore de referência de todos os tipos que mantiverem referências a esse tipo. Você pode expandir a árvore para percorrer todas as referências que estão mantendo esse tipo vivo. Na árvore, um valor de [CCW (ObjectType)] significa que o objeto está sendo mantido vivo por uma referência fora do código gerenciado (tais como o quadro XAML, código C++ ou JavaScript). Figura 19 mostra uma imagem da árvore de referência para nosso objeto suspeito de ItemDetailPage.


Figura 19 a árvore de referência para o tipo de ItemDetailPage em PerfView

Esta vista pode ver claramente que o ItemDetailPage está sendo realizada ao vivo pelo manipulador de eventos para o evento WindowSizeChanged, e esta é provavelmente a causa de vazamento de memória. O manipulador de eventos está sendo realizado a por algo fora do código gerenciado, neste caso a estrutura XAML. Se você olhar para um dos objetos XAML, você pode ver que eles estão também sendo mantidos vivos pelo mesmo manipulador de eventos. Por exemplo, a árvore de referência para o tipo de Windows.UI.Xaml.Controls.Button é mostrada na Figura 20.


Figura 20 a árvore de referência para o tipo de Windows.UI.Xaml.Controls.Button

Esta vista, você pode ver que todas as novas instâncias de interface do usuário.Xaml.Controls.Button está sendo mantido vivo por ItemDetailPage, que por sua vez está sendo mantido vivo pelo WindowSizeChangedEventHandler.

É muito claro neste ponto que corrigir o vazamento de memória é necessário para remover a referência ao ItemDetailPage, o manipulador de evento SizeChanged da seguinte forma:

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
  Window.Current.SizeChanged -= WindowSizeChanged;
}

Depois de adicionar este substituir a classe ItemDetailPage, as instâncias de ItemDetailPage não se acumulam com o tempo e nosso vazamento é fixo.

Os métodos descritos aqui dar-lhe algumas maneiras simples para analisar vazamentos de memória. Não se surpreenda se você se deparar com situações semelhantes. É muito comum para vazamentos de memória no Windows Store apps para ser causado por subscrever fontes de eventos de longa duração e não cancelar a inscrição dos mesmos; Felizmente, a cadeia de objetos vazou identificará claramente o problema. Isto também abrange casos de referências circulares em manipuladores de eventos através de linguagens como c# tradicional / vazamentos de memória Básica Visual causada por estruturas de dados ilimitado para armazenar em cache.

Em casos mais complexos, vazamentos de memória podem ser causados por ciclos entre objetos em aplicativos contendo c#, Visual Basic, JavaScript e C++. Nestes casos podem ser difícil de analisar porque muitos objetos na árvore de referência irão mostrar como externo para código gerenciado.

Considerações para Apps da loja do Windows que usam ambos JavaScript e c# ou Visual Basic

Para um aplicativo que é construído em JavaScript e usa c# ou Visual Basic para implementar componentes subjacentes, é importante lembrar que haverá duas separadas GCs gestão dois montes separados. Naturalmente, isto irá aumentar a memória usada pelo aplicativo. No entanto, o fator mais importante no consumo de memória do seu aplicativo continuará a ser seu gerenciamento de estruturas de dados grandes e suas vidas. Fazê-lo através de linguagens significa que você precisa ter o seguinte em mente:

Medir o impacto da limpeza atrasado uma pilha lixo-coletados normalmente contém objetos colecionáveis, aguardando o próximo GC. Você pode usar esta informação para investigar o uso de memória do seu aplicativo. Se você mede a diferença de uso de memória antes e após uma coleta de lixo induzidas manualmente, você pode ver quanta memória estava esperando "atrasado limpeza" contra a memória usada por objetos "ao vivo".

Para dual-GC apps, entendimento que este delta é muito importante. Devido a referências entre heaps, isso pode levar a uma sequência de coletas de lixo para eliminar todos os objetos colecionáveis. Para testar este efeito e para limpar seu TWS para que permaneçam apenas objetos vivos, você deve induzir repetidas, alternando GCs em seu código de teste. Você pode acionar um GC em resposta a um clique de botão (por exemplo), ou usando uma ferramenta de análise de desempenho que suporta-lo. Para disparar um GC em JavaScript, use o seguinte código:

window.CollectGarbage();
For the CLR, use the following:
GC.Collect(2, GCCollectionMode.Optimized);
GC.WaitForPendingFinalizers();

Você deve ter notado que, para o CLR, usamos apenas um GC.Recolha a chamada, ao contrário do código para induzir um GC na seção de diagnóstico de vazamentos de memória. Isso ocorre porque, neste caso queremos simular os reais padrões de GC em seu aplicativo que irá emitir apenas um GC ao mesmo tempo, Considerando que anteriormente nós queríamos tentar limpar objetos como muitos como possível. Observe que o recurso PerfView forçar GC não deve ser usado para medir a limpeza atrasada, porque ele pode forçar um JavaScript e um GC do CLR.

A mesma técnica deve ser usada para medir o seu uso de memória em suspender. Em um c# - ou JavaScript - ambiente único, que GC do língua será executado automaticamente em suspensão. No entanto, em c# ou Visual Basic e JavaScript híbrido apps, só o GC de JavaScript será executado. Isso pode deixar alguns itens colecionáveis no heap CLR que irá aumentar o conjunto de trabalho particular do seu aplicativo (PWS) enquanto suspenso. Dependendo de como esses itens são grandes, seu aplicativo poderia ser encerrado prematuramente em vez de estar suspenso (consulte a seção "Evitar segurando grandes referências em suspender" o artigo anterior).

Se o impacto sobre o PWS é muito grande, pode valer a pena invocar o GC do CLR no manipulador de suspender. No entanto, nada disto deve ser feito sem medir uma redução substancial no consumo de memória, porque em geral você deseja manter o trabalho feito em suspender a um mínimo (e em particular, nem de longe o 5 segundo tempo limite imposta pelo sistema).

Analisar os montes ao investigar o consumo de memória e depois eliminando qualquer impacto de limpeza posterior, é importante analisar o JavaScript heap e a pilha de .NET. Para o heap do .NET, a abordagem recomendada é usar a ferramenta PerfView, descrita na seção "analisar memória vazamentos no Windows Store Apps usando c# e Visual Basic", se você quer entender o consumo total de memória ou para investigar um vazamento.

Com a versão atual do PerfView, você é capaz de olhar para uma visualização combinada do JavaScript e .NET montes, permitindo que você veja todos os objetos através de linguagens gerenciadas e entender todas as referências entre eles.

Rua Chipalo é gerente de programa na equipe do Windows 8 XAML. Ele começou a trabalhar em Windows Presentation Foundation (WPF) em linha reta fora da faculdade. Cinco anos depois ele ajudou XAML evoluir através de três produtos (WPF, Silverlight e Windows 8 XAML) e várias versões. Durante o desenvolvimento do Windows 8, que possuía que tudo relacionado ao texto, impressão e desempenho para a plataforma XAML.

Dan Taylor é gerente de programa na equipe do Microsoft .NET Framework. No ano passado, Taylor tem trabalhado sobre o desempenho do .NET Framework CLR para Windows 8 e núcleo CLR para Windows Phone 8.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Deon Brewis, Mike Hillberg, Dave Hiniker e Ivan Naranjo