Este artigo foi traduzido por máquina.

JavaScript

Gerenciando Memória nos Aplicativos da Windows Store

David Tepper

 

Windows 8 destina-se a sentir-se fluido e vivos, deixando os usuários alternam rapidamente entre vários aplicativos para realizar várias tarefas e atividades. Os usuários esperam pop rapidamente e sair de diferentes experiências, e eles nunca querem sentir como eles têm que esperar por um app quando eles precisam usá-lo. Neste modelo, os apps raramente são finalizados pelo usuário; em vez deles freqüentemente estão alternados entre um estado de execução e suspensão. Apps são trazidos para o primeiro plano para o uso e, em seguida, mudou-se para o plano de fundo quando o usuário alterna para outro app — e ao mesmo tempo que os usuários esperam suas máquinas para não retardar ou sensação lenta, mesmo que eles abrem mais e mais aplicativos.

As investigações da equipe Microsoft Windows Application Experience, vimos que alguns apps da loja do Windows começam a encontrar problemas de recurso durante o uso prolongado. Erros de gerenciamento de memória em aplicativos podem agravar ao longo do tempo, levando ao uso de memória desnecessária e impactando negativamente a máquina global. Em nossos esforços para esmagar esses bugs em nossos próprios produtos, nós identificamos uma série de padrões repetitivos do problema, bem como comum correções e técnicas para escapar-lhes. Neste artigo, falarei sobre como pensar sobre gerenciamento de memória em seu Windows Store apps, bem como maneiras de identificar vazamentos de memória potencial. Também fornecerei algumas codificadas soluções para problemas comuns que a equipe tem observado.

Quais são os vazamentos de memória?

Qualquer cenário em um app que leva aos recursos que podem ser recuperados nem usados é considerado um vazamento de memória. Em outras palavras, se o aplicativo está segurando um pedaço de memória que o resto do sistema nunca será capaz de usar até que o aplicativo é encerrado e o aplicativo em si não está usando, é um problema. Esta é uma definição mais ampla do que a explicação típica de uma memória vazamento, "Alocado dinamicamente a memória que está inacessível no código," mas também é mais útil porque ela engloba problemas de utilização de recursos, outros semelhantes que podem afetar negativamente o usuário e o sistema. Por exemplo, se um aplicativo é armazenar dados que é acessíveis de todas as partes do código, mas os dados são usados apenas uma vez e nunca lançado mais tarde, é um vazamento de acordo com esta definição.

É importante ter em mente que às vezes os dados são armazenados na memória que nunca será usada simplesmente devido a ações do usuário em que instância particular. Desde que esta informação é potencialmente utilizável durante toda a vida do app ou é liberada quando não é mais necessária, não é considerado um vazamento, apesar de nunca ser usado.

Qual é o impacto?

Longe estão os dias quando as máquinas estavam em uma corrida para o céu para a disponibilidade de recursos. PCs estão ficando menores e mais portáteis, com menos recursos disponíveis do que seus antecessores. Trata-se fundamentalmente em desacordo com os padrões de uso cada vez mais comuns que envolvem comutação entre várias experiências rapidamente, com a expectativa de uma interface ágil e todo o conteúdo disponível imediatamente. Hoje, apps são multitudinária e vivo por longos períodos de tempo. Ao mesmo tempo, as máquinas têm menos memória para apoiá-los todos, e expectativas de desempenho do usuário nunca tem sido superiores.

Mas alguns megabytes de escape realmente faz uma diferença tão grande? Bem, a questão não é que alguns megabytes vazaram uma vez, é que vazamentos de memória no código composto muitas vezes ao longo do tempo como uso do app continua. Se um cenário leva a recursos irrecuperáveis, a quantidade de recursos irrecuperáveis vai crescer, geralmente sem limites, o usuário continua a repetir esse cenário. Isso degrada rapidamente a usabilidade do sistema como um todo, menos memória está disponível para outros processos, e leva os usuários a desempenho do sistema pobre atributo para seu aplicativo. Vazamentos de memória são mais graves quando eles aparecem em:

  • Tarefas frequentes (como o próximo quadro de um vídeo de decodificação)
  • Tarefas que não exigem interação do usuário para iniciar (por exemplo, auto-Salvando um documento periodicamente)
  • Cenários que se estendem por longos períodos (como tarefas em segundo plano)

Vazamentos nestas situações (e em geral) podem aumentar significativamente o consumo de memória do seu aplicativo. Não só pode esta levar a uma crise de utilização de recursos para todo o sistema, também faz seu app muito mais propensos a ser finalizado em vez de suspensão quando não estiver em uso. Encerrado apps levar mais tempo para reativar que apps suspensos, reduzindo a facilidade com que os usuários podem experimentar seus cenários. Para detalhes completos sobre como o Windows usa um Gerenciador de tempo de vida do processo para recuperar memória de aplicativos não utilizados, consulte o blog Building Windows 8 post no bit.ly/JAqexg.

Assim, vazamentos de memória são ruins, mas como você encontrá-los? Nas próximas seções que eu vou sobre onde e como procurar estas questões, e, em seguida, dê uma olhada por que eles ocorrem e que você pode fazer sobre eles.

Diferentes tipos de memória

Nem todos os bits são distribuídos igualmente. Windows mantém registro de sobreviventes diferentes ou modos de exibição, de uso de memória de um app para facilitar as tarefas de análise de desempenho. Para entender melhor como detectar vazamentos de memória, é útil saber sobre estas classificações de memória diferentes. (Esta seção pressupõe algum conhecimento de gerenciamento de memória do sistema operacional através da paginação.)

Conjunto de trabalho particular o conjunto de páginas seu aplicativo está usando para armazenar seus próprios dados exclusivos. Quando você pensa em "uso de memória do aplicativo", este é provavelmente o que você está pensando.

Compartilhado conjunto de trabalho o conjunto de páginas de seu app é utilizando mas não possuída por seu processo. Se seu aplicativo está usando um tempo de execução compartilhado ou quadro, DLLs comuns ou outro recurso multiprocess, esses recursos vão ocupar certa quantidade de memória. Conjunto de trabalho compartilhado é a medida desses recursos compartilhados.

Total de trabalho conjunto (TWS) às vezes simplesmente chamado de "conjunto de trabalho", isto é a soma do conjunto de trabalho particular e o conjunto de trabalho compartilhado.

A TWS representa todo o impacto do seu aplicativo no sistema, por isso usam as técnicas de medição, que vou descrever esse número. No entanto, quando rastrear problemas em potencial, você pode encontrá-lo útil para investigar o privado ou compartilhado de trabalho conjuntos separadamente, como isso pode te dizer se é seu app que está vazando, ou um recurso que o aplicativo está usando.

Descobrindo vazamentos de memória

A maneira mais fácil de descobrir o quanto de memória seu aplicativo está usando em cada categoria é usar o built-in Gerenciador de tarefas do Windows.

  1. Inicie o Gerenciador de tarefas pressionando Ctrl + Shift + Esc e clique em mais detalhes na parte inferior.
  2. Clique no item de menu de opções e certifique-se de que "Sempre no topo" está marcada. Isso impede que seu aplicativo de ir o fundo e suspensão enquanto você está olhando no Gerenciador de tarefas.
  3. Inicie seu aplicativo. Uma vez que o app aparece no Gerenciador de tarefas, clique com botão direito nele e clique em "Ir para detalhes."
  4. Perto do topo, botão direito do mouse em qualquer coluna e vá para "Selecionar colunas."
  5. Você vai notar as opções aqui para conjunto de trabalho compartilhado e privado (entre outros), mas para o momento, apenas certifique-se de que o "Conjunto de trabalho (memória)" está marcado e clique em OK (ver Figura 1).
  6. O valor que você verá é a TWS para seu aplicativo.


Figura 1 verificando o Total de trabalho conjunto no Gerenciador de tarefas do Windows

Para descobrir rapidamente possíveis vazamentos de memória, deixe seu app e Gerenciador de tarefas aberto e anote as TWS do seu aplicativo. Escolha agora um cenário em seu aplicativo que você deseja testar. Um cenário é composto de ações que um usuário típico seria executado, muitas vezes, envolvendo geralmente não mais de quatro passos (navegar entre páginas, realizando uma pesquisa e assim por diante). Execute o cenário como um usuário faria e observe qualquer aumento da TWS. Em seguida, sem fechar o app, atravessar o cenário novamente, começando do início. Faça isso 10 vezes e registrar o TWS após cada etapa. É normal a TWS aumentar pela primeira vez algumas iterações e, em seguida, o platô.

Fez uso de memória do seu aplicativo aumentar cada vez que o cenário foi executado, sem nunca redefinir seu nível original? Se assim, é possível você tem um vazamento de memória nesse cenário e você vai querer dar uma olhada as sugestões a seguir. Se não, ótimo! Mas certifique-se de verificar outros cenários em seu aplicativo, particularmente aqueles que são muito comuns, ou que usam grandes recursos, como imagens. Evitar executar esse processo em uma máquina virtual ou sobre a área de trabalho remota, no entanto; esses ambientes podem causar falsos positivos quando à procura de vazamentos e aumentar seus números de uso de memória para além do seu valor real.

Usando o Windows 8 ferramentas de detecção de vazamento de memória

Você pode se perguntar se você pode usar ferramentas de detecção de vazamento de memória existentes para identificar problemas com sua app Store do Windows. A menos que essas ferramentas são atualizadas para trabalhar com o Windows 8, é muito provável que vai ser "confuso" pela falta do app de desligamento normal (que foi substituída pela suspensão). Para contornar esse problema, você pode usar a funcionalidade de "Saída" AppObject diretamente fechar o app em forma ordenada, em vez de fechá-lo com força através de terminação externa:

  • C++—CoreApplication::Exit();
  • C#—Application.Current.Exit();
  • JavaScript—window.close();

Ao usar esta técnica, certifique-se de que você não enviar o produto com este código no lugar. Seu aplicativo não invoca qualquer código que dispara na suspensão e vontade precisa ser reativado (em vez de retomada) cada vez que for aberto. Esta técnica deve ser usada somente para fins de depuração e removida antes de enviar o aplicativo para o armazenamento do Windows.

Fontes comuns de vazamentos de memória

Nesta seção, vamos discutir algumas armadilhas comuns que vimos os desenvolvedores atravessam em todos os tipos de aplicativos e linguagens, bem como abordar estas questões em seus aplicativos.

Manipuladores de eventos manipuladores de eventos são de longe o mais comuns fontes de vazamentos de memória que vimos nos apps da loja do Windows. A questão fundamental é a falta de compreensão sobre como manipuladores de eventos de trabalho. Manipuladores de eventos não são apenas código que é executado; eles são alocados os objetos de dados. Eles mantiverem referências a outras coisas, e o que eles mantiverem referências a pode não ser óbvio. Conceitualmente, a instanciação e o registo de um manipulador consiste de três partes:

  1. A origem do evento
  2. O método de manipulador de evento (sua implementação)
  3. O objeto que hospeda o método

Como exemplo, vamos olhar para um app chamado LeakyApp, mostrado no Figura 2.

Figura 2-LeakyApp

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

O código de LeakyApp mostra as três partes de um manipulador de eventos:

  • Window.Current é o objeto que origina (incêndios) o evento.
    • Um ItemDetailPage instância é o objeto que recebe (pias) o evento.
  • WindowSizeChanged é o método de manipulador de evento na instância ItemDetailPage.

Depois de registrar para a notificação de evento, o objeto da janela atual tem uma referência ao manipulador de evento em um objeto ItemDetailPage, como mostrado na Figura 3. Esta referência faz com que o objeto ItemDetailPage permanecer vivo enquanto o objeto de janela atual permanece vivo, ou até que o objeto de janela atual cai a referência para o Item­DetailPage instância (ignorando, por agora, outras referências externas para esses objetos).


Figura 3 referência ao manipulador de evento

Observe que, para a instância de ItemDetailPage operar corretamente, enquanto ele está vivo o tempo de execução do Windows (WinRT) transitivamente mantém todos os recursos que está usando a instância vivo. Deve a instância contêm referências a grandes alocações como matrizes ou imagens, essas alocações vão ficar vivas para o tempo de vida da instância. Com efeito, registrar um manipulador estende a vida útil da instância do objeto que contém o manipulador de eventos e todas as suas dependências, para coincidir com o tempo de vida da fonte de evento. Claro que, até agora, isso não é um vazamento de recursos. É simplesmente a conseqüência de inscrever-se para um evento.

O ItemDetailPage é semelhante a todas as páginas em um aplicativo. Ele é usado quando o usuário navega para a página, mas não é mais necessário quando eles navegam para uma página diferente. Quando o usuário navega de volta para o ItemDetailPage, o aplicativo normalmente cria uma nova instância da página e a nova instância registra com a janela atual para receber eventos SizeChanged. O bug neste exemplo, porém, é que quando o usuário navega longe do ItemDetailPage, a página não cancelar o registro de seu manipulador de eventos do atual evento SizeChanged janela. Quando o usuário navega longe do ItemDetailPage, a janela atual ainda tem uma referência para a página anterior e a janela atual continua a fogo SizeChanged eventos para a página. Quando o usuário navega de volta para o ItemDetailPage, essa nova instância também registra com a janela atual, conforme mostrado no Figura 4.


Figura 4 segunda instância registrada com a janela atual

Cinco navegações mais tarde, cinco objetos ItemDetailPage são registradas com a janela atual (consulte Figura 5) e todos os seus recursos dependentes são mantidos vivos.


Figura 5 cinco objetos registrados com a janela atual

Essas instâncias de ItemDetailPage não mais usados são recursos que nunca podem ser usados ou recuperados; eles são efetivamente vazados. Se você pegar uma coisa longe deste artigo, certifique-se de que é cancelar o registro de manipuladores de eventos quando eles já não são necessários a melhor maneira de evitar o vazamento de memória mais comuns.

Para corrigir o problema no LeakyApp, é preciso remover a referência ao manipulador de evento SizeChanged da janela atual. Isso pode ser feito por cancelar o manipulador de eventos quando a página sai do modo de exibição, assim:

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 e o vazamento é fixo.

Nota que este tipo de problema pode ocorrer com qualquer objeto — qualquer objeto de longa duração mantém vivo tudo o que ele faz referência. Eu chamo manipuladores de eventos aqui porque eles são fonte de longe o mais comum deste problema — mas, como discutirei, limpando objetos como eles são necessários não é a melhor maneira de evitar vazamentos de memória grande.

Circular referências em manipuladores de eventos que cruzar limites de GC ao criar um manipulador para um evento específico, você começa especificando uma função que será chamada quando o evento é acionado, e, em seguida, anexar o manipulador para um objeto que receberá o evento em questão. Quando o evento é acionado na verdade, a função de manipulação tem um parâmetro que representa o objeto que inicialmente recebeu o evento, conhecido como a "fonte de evento". No botão clique em manipulador de eventos a seguir, o parâmetro "remetente" é a origem do evento:

private void Button_Click(object sender, RoutedEventArgs e)
{
}

Por definição, a fonte do evento tem uma referência para o manipulador de eventos, senão a fonte não poderia disparar o evento. Se você capturar uma referência para a fonte de dentro do manipulador de evento, o manipulador tem agora uma referência para a fonte e você criou uma referência circular. Vamos olhar para um padrão bastante comum desta ação:

// gl is declared at a scope where it will be accessible to multiple methods
Geolocator gl = new Geolocator();
public void CreateLeak()
{           
  // Handle the PositionChanged event with an inline function
  gl.PositionChanged += (sender, args) =>
    {
      // Referencing gl here creates a circular reference
      gl.DesiredAccuracy = PositionAccuracy.Default;
    };
}

Neste exemplo, gl e remetente são os mesmos. Referenciar gl na função lambda cria uma referência circular porque a fonte está referenciando o manipulador e vice-versa. Normalmente este tipo de referência circular não seria um problema, porque os catadores de lixo CLR e JavaScript (GCs) são inteligentes o suficiente para lidar com tais casos. No entanto, problemas podem surgir quando um lado da referência circular não pertence a um ambiente de GC ou pertence a um ambiente diferente do GC.

Geolocator é um objeto WinRT. WinRT objetos são implementados em C/C++ e, portanto, usam um sistema de contagem de referência em vez de um GC. Quando o CLR GC tenta limpar esta referência circular, ele não pode limpar a gl por conta própria. Da mesma forma, a contagem de referência para a gl nunca vai chegar a zero, então o lado de C/C++ de coisas não é eliminado ou.

Naturalmente, este é um exemplo muito simples para demonstrar o problema. Se não fosse um único objeto, mas em vez disso, um grande agrupamento de elementos de interface do usuário como um painel (ou em JavaScript, uma div)? O vazamento seria abranger todos os objetos e rastrear a fonte seria extremamente difícil.

Existem várias reduções no lugar para que muitos destes circularities podem ser detectados e limpos pelo GC. Por exemplo, referências circulares envolvendo uma fonte de evento do WinRT que está em um ciclo com código JavaScript (ou referências circulares com um objeto XAML como a fonte de evento) corretamente são recuperadas. No entanto, nem todas as formas de circularities são abrangidas (como um evento com um manipulador de eventos de JavaScript), e como o número e a complexidade das referências à fonte do evento crescerem, atenuações de especiais do GC tornam-se menos garantidas.

Se você precisar criar uma referência para a fonte do evento, você pode cancelar sempre explicitamente o manipulador de eventos ou nulo de referência mais tarde para derrubar a circularidade e impedir eventuais fugas (isto vai para trás ao raciocínio sobre o tempo de vida de objetos que você criar). Mas se o manipulador de eventos nunca contém uma referência para a fonte, você não precisa confiar na plataforma fornecida mitigação ou código explícito para evitar o que pode ser um problema muito grande de utilização de recursos.

Usando estruturas de dados ilimitado para cache em muitos aplicativos, faz sentido para armazenar algumas informações sobre as atividades recentes do usuário para melhorar a experiência. Por exemplo, imagine um aplicativo de pesquisa que exibe os últimos cinco consultará o usuário digitou. Um padrão de codificação para alcançar este objectivo é simplesmente armazenar cada consulta de uma lista ou outra estrutura de dados e, quando chega a hora de dar sugestões, recuperar os cinco primeiros. O problema com esta abordagem é que, se o app é deixado em aberto por longos períodos, a lista vai crescer sem limites, eventualmente tomando uma grande quantidade de memória desnecessária.

Infelizmente, um GC (ou qualquer outro gerenciador de memória) não tem razão sobre estruturas de dados muito grande, mas acessível, que nunca será usada. Para evitar o problema, manter um limite no número de itens que você armazenar em cache. Fase mais velhos dados regularmente e não confiar em seu aplicativo que está sendo encerrado para liberar esses tipos de estruturas de dados. Se as informações estão sendo armazenadas são particularmente fácil para reconstituir ou temporais, considere esvaziar o cache completamente quando a suspensão. Se não, salve o cache local estado e liberar o recurso de memória; ele pode ser readquirida no currículo.

Evite segurar grandes referências em suspender

Não importa a língua, segurar grandes referências, embora suspenso pode levar a problemas UX. Seu app vai ficar suspenso por enquanto o sistema é capaz das solicitações de outros processos em execução de serviço sem necessitar de memória adicional que só pode ser recuperada pelo encerramento de apps. Porque ficar suspensa significa que seu aplicativo pode ser acessado mais facilmente pelo usuário, é de seu interesse para manter seu espaço de memória pequeno durante a suspensão.

Uma maneira simples de fazer isso é simplesmente libertar todas as referências a objetos grandes quando a suspensão que pode ser reconstituída no currículo. Por exemplo, se seu aplicativo está segurando uma referência em memória para dados de aplicativo local, liberando a referência pode diminuir significativamente seu conjunto de trabalho particular e é fácil readquirir no currículo, porque esses dados não vão em qualquer lugar. (Para obter mais informações sobre dados de aplicativo, consulte bit.ly/MDzzIr.)

Para liberar uma variável completamente, atribuir a variável (e todas as referências a variável) para null. Em C++, este será imediatamente recuperar a memória. Para aplicativos Microsoft .NET Framework e JavaScript, o GC será executado quando o aplicativo está suspenso para recuperar a memória para essas variáveis. Esta é uma abordagem de defesa em profundidade para garantir o gerenciamento de memória correta.

Nota, entretanto, que se seu aplicativo é escrito em JavaScript e tem alguns componentes do .NET, o .NET GC não vai ser executado em suspender.

Gerenciamento de memória em aplicativos JavaScript Windows Store

Aqui estão algumas dicas para criar apps da loja do Windows recursos eficientes em JavaScript. Estas são as correções recomendadas para problemas comuns que temos visto em nossos próprios aplicativos e projetando com eles em mente vai ajudar a afastar muitos problemas potenciais antes que eles causam dores de cabeça.

Use ferramentas de qualidade de código um recurso muitas vezes negligenciado, ferramentas de qualidade de código freeware estão disponíveis para todos os desenvolvedores de JavaScript na Web. Estas ferramentas inspecionam seu código para muitos dos problemas comuns, incluindo vazamentos de memória, e pode ser sua melhor aposta para a captura de problemas mais cedo. Duas ferramentas úteis são JSHint (jshint.com) e JSLint (jslint.com).

Use o modo estrito JavaScript tem um modo "estrito" que limita a maneira que você pode usar variáveis em seu código. Estas limitações se apresentam como erros de tempo de execução que são jogados quando forem violadas as regras extras. Tais codificação restrições podem ajudá-lo a evitar vazamentos de memória comum, como implicitamente declarando variáveis no escopo global. Para obter mais informações sobre o modo estrito, seu uso e as restrições impostas, confira o artigo da biblioteca MSDN, "Modo estrito (JavaScript)," em bit.ly/RrnjeU.

Evitar referências circulares de fechamento JavaScript possui um sistema bastante complicado de armazenar as referências a variáveis, sempre que uma função lambda (ou embutido) é usado. Basicamente, em ordem para a função embutida executar corretamente quando ele é chamado, JavaScript armazena o contexto de variáveis disponíveis em um conjunto de referências, conhecido como um fechamento. Essas variáveis são mantidas vivas na memória até que a própria função inline não é referenciada. Vamos dar uma olhada em um exemplo:

myClass.prototype.myMethod = function (paramA, paramB) {
  var that = this;
  // Some code
  var someObject = new someClass(
    // This inline function's closure contains references to the "that" variable,
    // as well as the "paramA" and "paramB" variables
    function foo() {
      that.somethingElse();
    }
  );
  // Some code: someObject is persisted elsewhere
}

Depois someObject é persistente, a memória referenciada por "que", "paramA" e "paramB" não vai ser recuperado até someObject é destruído ou libera sua referência para a função embutida, que ele foi passado no Construtor someClass.

Problemas podem ocorrer com os fechos de funções embutidas se a referência à função inline não é liberada, como as referências de encerramento irão residir permanentemente na memória, causando um vazamento. A maneira mais comum que isso ocorre é quando um fechamento contém uma referência circular a mesmo. Isso geralmente acontece quando uma função embutida faz referência a uma variável que faz referência a função embutida:

function addClickHandler(domObj, paramA, paramB, largeObject) {
  domObj.addEventListener("click",
  // This inline function's closure refers to "domObj", "paramA",
  // "paramB", and "largeObject"
    function () {
      paramA.doSomething();
      paramB.somethingElse();
    },
  false);
}

Neste exemplo, domObj contém uma referência para a função embutida (através de ouvinte de evento) e encerramento da função inline contém uma referência a ela. Porque largeObject não estiver sendo usado, a intenção é que ele vai fora do escopo e obter recuperados; no entanto, a referência de encerramento mantém-lo e domObj viva na memória. Esta referência circular irá resultar em um vazamento até domObj remove a referência de ouvinte de evento ou obtém nulled fora e lixo coletado. A maneira correta de realizar algo como isto é usar uma função que retorna uma função que executa suas tarefas, como mostrado na Figura 6.

Figura 6 usando o escopo da função para evitar referências circulares de encerramento

function getOnClick(paramA, paramB) {
  // This function's closure contains references to "paramA" and "paramB"
  return function () {
    paramA.doSomething();
    paramB.somethingElse();
  };
}
function addClickHandlerCorrectly(domObj, paramA, paramB, largeObject) {
  domObj.addEventListener(
    "click",
  // Because largeObject isn't passed to getOnClick, no closure reference
  // to it will be created and it won't be leaked
  getOnClick(paramA, paramB),
  false);
}

Com esta solução, é eliminada a referência de encerramento a domObj, mas as referências a paramA e paramB ainda existem, como eles são necessários para a implementação de manipulador de evento. Para ter certeza não vazamento paramA ou paramB, você ainda precisará cancelar o registro o ouvinte de evento ou apenas esperar por eles para obter automaticamente recuperada quando domObj Obtém o lixo coletado.

Revogar todos os URLs criados por URL.createObjectURL uma maneira comum para carregar mídia para um elemento de áudio, vídeo ou img é usar o método URL.createObjectURL para criar uma URL que pode usar o elemento. Quando você usa esse método, ele informa ao sistema para manter uma referência interna para sua mídia. O sistema usa essa referência interna para transmitir o objeto para o elemento apropriado. No entanto, o sistema não sabe quando os dados não é necessário, portanto, ele mantém a referência interna viva na memória até que ele explicitamente disse para liberá-lo. Essas referências internas podem consumir grandes quantidades de memória e é fácil para acidentalmente retêm-los desnecessariamente. Há duas maneiras de liberar essas referências:

  1. Você pode revogar a URL explicitamente chamar o método URL.revokeObjectURL e passando a URL.
  2. Você pode dizer que o sistema automaticamente revogar a URL depois que ele é usado uma vez por configuração a propriedade de oneTimeOnly de URL.createObjectURL para true:
var url = URL.createObjectURL(blob, {oneTimeOnly: true});

Usar referências fracas para objetos temporários Imagine que você tem um grande objeto referenciado por um nó de Document Object Model (DOM) que você precisa para usar em várias partes do seu aplicativo. Agora, suponha que, em qualquer ponto, o objeto pode ser liberado (por exemplo, node.innerHTML = ""). Como você faz certo evitar mantendo referências ao objeto que podem ser totalmente recuperado a qualquer momento? Felizmente, o tempo de execução do Windows fornece uma solução para este problema, o que permite que você armazene "fracas" referências a objetos. Uma referência fraca não bloquear o GC de objeto refere-se a limpeza e, quando cancelado, ele pode retornar o objeto ou nulo. Para entender melhor como isso pode ser útil, dê uma olhada no exemplo em Figura 7.

Figura 7 JavaScript Memory Leak

function addOptionsChangedListener () {
  // A WinRT object
  var query = Windows.Storage.KnownFolders.picturesLibrary.createFileQuery();
  // 'data' is a JS object whose lifetime will be associated with the  
  // behavior of the application.
Imagine it is referenced by a DOM node, which
  // may be released at any point.
// For this example, it just goes out of scope immediately,
  // simulating the problem.
var data = {
    _query: query,
    big: new Array(1000).map(function (i) { return i; }),
    someFunction: function () {
      // Do something
    }
  };
  // An event on the WinRT object handled by a JavaScript callback,
  // which captures a reference to data.
query.addEventListener("optionschanged", function () {
    if (data)
      data.someFunction();
  });
  // Other code ...
}

Neste exemplo, o objeto de dados não está sendo recuperado porque está sendo referenciado pelo ouvinte de evento na consulta. Porque a intenção do app foi claro o objeto de dados (e não mais serão feitas tentativas de fazê-lo), este é um vazamento de memória. Para evitar isso, o grupo WeakWinRTProperty API pode ser usado com a seguinte sintaxe:

msSetWeakWinRTProperty(WinRTObj, "objectName", objectToStore);

WinRTObj é qualquer objeto de WinRT que suporta IWeakReference, objectName é a chave para acessar os dados e objectToStore são os dados a ser armazenado.

Para recuperar a informação, use:

var weakPropertyValue = msGetWeakWinRTProperty(WinRTObj, "objectName");

WinRTObj é o WinRT objeto onde a propriedade era armazenada e objectName é a chave ao abrigo do qual os dados foram armazenados.

O valor de retorno é null ou o valor originalmente armazenado (objectToStore).

Figura 8 mostra uma maneira de corrigir o vazamento na addOptions­ChangedListener função.

Figura 8 usando referências fracas para evitar um vazamento de memória

function addOptionsChangedListener() {
  var query = Windows.Storage.KnownFolders.picturesLibrary.createFileQuery();
  var data = {
    big: new Array(1000).map(function (i) { return i; }),
    someFunction: function () {
      // Do something
    }
  };
  msSetWeakWinRTProperty(query, "data", data)
  query.addEventListener("optionschanged", function (ev) {
    var data = msGetWeakWinRTProperty(ev.target, "data");
    if (data) data.someFunction();
  });
}

Porque a referência ao objeto de dados é fraca, quando outras referências a ele são removidas, será coletado e sua memória recuperada.

Arquitetura de armazenamento Windows Apps usando JavaScript

Projetar seu aplicativo com a utilização dos recursos em mente pode reduzir a necessidade de correções local e práticas de codificação específicos de gerenciamento de memória, tornando sua aplicação mais resistente a vazamentos, desde o início. Ele também permite a criação de salvaguardas que tornam mais fácil para identificar vazamentos quando eles acontecem. Nesta seção, discutirei dois métodos de arquitetar uma app Store do Windows escrita com JavaScript que pode ser usada independentemente ou em conjunto para criar um aplicativo de recurso eficiente que é fácil de manter.

Elimine a arquitetura elimine a arquitetura é uma ótima maneira de parar vazamentos de memória no seu início, por ter uma forma consistente, fácil e robusta para recuperar os recursos. O primeiro passo para projetar que seu aplicativo com esse padrão em mente é garantir que cada classe ou objeto grande implementa uma função (normalmente chamado dispose) que recupera a memória associada com cada objeto-referências. A segunda etapa é implementar uma função amplamente acessível (também normalmente chamado dispose) que chama o método dispose em um objeto passado como um parâmetro e, em seguida, nulos fora o objeto em si:

 

var dispose = function (obj) {
  /// <summary>Safe object dispose call.</summary>
  /// <param name="obj">Object to dispose.</param>
  if (obj && obj.dispose) {
    obj.dispose();                
  }
  obj = null;
};

O objetivo é que o aplicativo leva a uma estrutura de árvore, com cada objeto com um método dispose interna que libera os seus próprios recursos, chamando o dispose método em todos os objetos referências e assim por diante. Dessa forma, para liberar totalmente um objeto e todas as suas referências, tudo que você precisa fazer é chamada de dispose(obj)!

Em cada transição do cenário principal em seu aplicativo, simplesmente chamar dispose em todos os de nível superior os objetos que não são mais necessárias. Se você quiser começar a fantasia, você pode ter todos esses objetos de nível superior ser parte de um objeto grande "cenário". Ao alternar entre cenários, você simplesmente chamar dispose sobre o objeto de nível superior do cenário e instanciar um novo cenário para que o app está alternando.

Timpanismo arquitetura a arquitetura "Bloat" permite que você mais facilmente identifica quando ocorrem vazamentos de memória, tornando objetos realmente grande direito antes de liberar a eles. Dessa forma, se o objeto não é lançado, o impacto TWS do seu aplicativo será óbvio. Naturalmente, este padrão só deve ser usado durante o desenvolvimento. Um app deve nunca acompanham este código no lugar, como suspenso de cravação memória uso (mesmo temporariamente) pode forçar a máquina de um usuário para finalizar outros apps.

Para inchar artificialmente um objeto, você pode fazer algo tão simples como anexar uma matriz muito grande para ele. Usando a sintaxe de join rapidamente preenche a matriz inteira com alguns dados, tornando qualquer objeto que é ligado ao visivelmente maior:

var bloatArray = [];
bloatArray.length = 50000;
itemToBloat.leakDetector = bloatArray.join("#");

Para usar esse padrão de forma eficaz, você precisa de uma boa maneira para identificar quando um objeto é suposto para ser liberado pelo código. Você pode fazer isso manualmente para cada objeto que você liberar, mas há duas maneiras de melhores. Se você estiver usando a arquitetura de Dispose discutida, basta adicione o código do inchaço no método dispose do objeto em questão. Dessa forma, uma vez que dispose é chamado, você saberá se o objeto realmente tinha todas as suas referências ou não. A segunda abordagem é usar o evento JavaScript DOMNodeRemoved para todos os elementos que estão no DOM. Porque este evento é acionado antes que o nó é removido, você pode inchar o tamanho desses objetos e ver se eles realmente estão recuperados.

Observe que, por vezes, o GC vai levar algum tempo para realmente recuperar memória não utilizada. Ao testar um cenário para vazamentos, se o app parece ter crescido muito rapidamente, esperar um pouco para confirmar um vazamento; o GC pode não ter feito uma passagem ainda. Se, após a espera, a TWS é ainda elevado, tente o cenário novamente. Se TWS do app é ainda grande, é extremamente provável que há um vazamento. Você pode aprimorar na fonte removendo sistematicamente esse código de inchar os objetos em seu aplicativo.

Ir para a frente

Espero que eu tenho dado uma base sólida para identificar, diagnosticar e reparar vazamentos de memória em seus Windows loja apps. Vazamentos, muitas vezes resultado de mal-entendidos de como ocorrem a recuperação e a alocação de dados. Conhecimento destas nuances — combinado com truques fáceis, como explicitamente anulação sem referências a variáveis grandes — vai um longo caminho para garantir a eficientes apps que não abrandar máquinas dos usuários, mesmo em dias de uso. Se você está olhando para obter mais informações, você pode conferir um artigo do MSDN Library pela equipe do Internet Explorer que cobre tópicos relacionados, "Noções básicas sobre e resolvendo Internet Explorer vazamento padrões, bit.ly/Rrta3P.

David Tepper é gerente de programa na equipe de experiência do Windows aplicativo. Ele tem trabalhado no design do modelo de aplicativo e implantação de aplicativos, desde 2008, focando principalmente o desempenho dos aplicativos de armazenamento do Windows e como aqueles apps podem estender o Windows para fornecer profundamente integrado a funcionalidade*.***

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Jerry Dunietz, Mike Hillberg, Mathias Jourdain, Kamen Moutafov, Brent reitor e Chipalo Street