WSMonitor e Actionscript 3 Garbage Collector: Monitorando a memória no Adobe Flex e Flash
Navegando pelo site da Adobe encontrei o WSMonitor, ferramenta super simples para espiar a quantidade de memória usada pelos aplicativos Flash, rodando no computador.
Não é uma ferramenta essencial para a maioria das aplicações desenvolvidas, mas se o seu SWF faz uso extensivo de criação dinâmica de classes e você não confia no Actionscript Garbage Collector, está aí uma boa opção para monitorá-lo.
Para quem não sabe, quando uma instância de uma classe é criada no Actionscript 3, depois de usá-la, o programador não consegue destruí-la como em linguagens de programações tradicionais. O responsável por desalocar a memória de uma instância fora de uso é o Garbage Collector, que segundo a Adobe, destrói a instância logo após perceber que a mesma perdeu todas as referências.
O tema é complexo pois testes comprovam que o Garbage Collector é instável. Não consegue-se prever quando este irá liberar a memória de instâncias não usadas e muitas vezes o Garbage Collector demora ou falha.
Para aplicativos que usam extensivamente a memória, como editores de texto, editores de imagens, etc, vale a pena dar um conferida no WSMonitor.
Mais sobre o Garbage Collector
A liberação de memória no Actionscript 3 é um tema confuso, pois não existe documentação oficial da Adobe explicando, em detalhes, como o processo funciona. Porém, alguns profissionais de alto calibre dispõem artigos convincentes sobre o tema e a comunidade apoia-se sobre essas vozes.
Sabe-se que a linguagem Actionscript 3 é totalmente baseada em classes. Retirando os tipos primitivos, que são Int, Uint, Number, Boolean e String, todos os outros tipos de variáveis são classes onde existe a necessidade de chamar o operador New para criar uma instância da classe.
O operador New retorna apenas uma referência da instância e não a instância em si, o que significa que em Actionscript 3 os valores das variáveis são ponteiros para instâncias.
Na expressão variavel1 = variavel2, a primeira recebe a referência da instância armazenada em variavel2. Veja que tudo funciona com referências e não valores.
Segundo Grant Skinner, o Garbage Collector (Coletor de Lixo, tradução literal), é responsável por liberar a memória de instâncias inativas. Uma instância inativa é aquela cuja referência não está armazenada em nenhuma variável.
Grant também diz que o Actionscript 3 usa dois métodos para detectar se uma instância está inativa:
1) Reference Counting (Contador de Referências)
2) Mark and Sweep (Marcar e Varrer)
1. Reference Counting (Contador de Referências)
O primeiro método, Reference Counting usa uma idéia muito simples. Existe um contador de referências para cada instância adquirida com o operador New. Quando uma referência é criada, o contador de referências é incrementado. Quando uma referência é destruída, o contador de referências é decrementado. Se o contador de referências atinge o valor ZERO, a instância é desalocada (destruída).
Exemplo:
-
function teste():void {
-
var a:Array = new Array();
-
}
No trecho acima uma instância da classe Array é criada pelo operador New. Na variável a armazena-se a referência da instância (cuidado, armazena-se a referência e não a instância em si).
NOTA Outros nomes para referência , usados por programadores, são: endereço de memória; ponteiro; ou mesmo só endereço (da instância).
Quando a função teste() é chamada, como a variável a recebe uma referência, o contador interno de referências da instância criada é incrementado.
Quando a função retorna, a variável a perde o escopo e é destruída. Como a referência é perdida, o contador de referências da instância é decrementado, chegando ao valor ZERO. Nesse caso, o Garbage Collector destrói a instância em questão.
Outro Exemplo:
-
function outroteste():void {
-
// nova instância criada e referência da
-
// instância tipo Sprite armazenada em 'variavel1'.
-
// O contador de referências vale 1
-
var variavel1:Array = new Sprite();
-
-
// a mesma referência do tipo Sprite é armazenada
-
// em 'variavel2'. O contador de referências vale 2.
-
var variavel2:Array = variavel1;
-
-
// aqui, 'variavel2' está recebendo uma referência
-
// da instância do tipo Shape, que acaba de ser
-
// criada pelo operador New. Ou seja, o contador
-
// de referências da instância do tipo Sprite é
-
// decrementado, pois a referência foi perdida, e
-
// o contador de referências da instância do tipo
-
// Shape é incrementado.
-
var variavel2:Array = new Shape();
-
-
// nesse momento, o contador de referências da
-
// instância Sprite e Shape valem 1.
-
-
// aqui, variavel1 recebe o valor null, portanto a
-
// referência para a instância do tipo Sprite é
-
// perdida, o que faz com que o contador de
-
// referências seja decrementado, chegando ao
-
// valor 0
-
variavel1 = null;
-
-
}
-
// depois que a função retorna, variavel2 perde o
-
// escopo, portanto é destruída junto com a
-
// referência. Com a referência perdida, o
-
// contador de referências da instância tipo
-
// Shape é decrementado e também chega a 0.
Ambas as instâncias tem a memória liberada pelo Garbage Collector.
2. Mark and Sweep (Marcar e Varrer)
O segundo método é necessário porque o contador de referências, em alguns casos, falha na detecção de instâncias inativas.
Vamos supor o exemplo seguinte:
-
var objA:ClasseX = new ClasseX();
-
var objB:ClasseY = new ClasseY();
-
-
objA.propriedade = objB;
-
-
objA = null;
-
objB = null;
Veja que:
a) 2 instâncias são criadas com o operador New. Uma para ClasseX e outra para ClasseY.
b) Quando a referência é armazenada em objA, o contador de referências da instância ClasseX é incrementado.
c) Quando a referência é armazenada em ObjB, o contador de referências da instância ClasseY é incrementado.
d) Quando a referência de ClasseY é armazenada em objA.propriedade, o contador de referências da classe ClasseY é incrementado novamente.
e) Quando coloca-se o valor null em objA, o contador da instância ClasseX é decrementado.
f) Quando coloca-se o valor null em objB, o contador da instância ClasseY é decrementado.
Repare que depois de todo o processo, o contador de referências da instância ClasseY ainda vale 1, mesmo com objA e objB setados para null indicando que não pode-se mais acessar as instâncias! Nesse caso, pelo método anterior, a instância de ClasseY não é destruída pelo Garbage Collector, o que implica em dizer que o aplicativo estará consumindo memória à toa.
É aqui que entra em jogo o método Mark and Sweep.
O Mark and Sweep é uma técnica comum empregada por programadores. Foi introduzida primeiramente, em meados de 1959, por John McCarthy e o processo consiste em percorrer listas com referências em uso pelo aplicativo e marcar as respectivas instâncias. As instâncias marcadas não são destruídas, e as que sobram, o Garbage Collector remove (são varridas).
O Flash mantém várias listas com referências em uso. O Stage, por exemplo, é a raiz de uma árvore de referências que, teoricamente, o Garbage Collector acessa e através das referências encontradas, marca as devidas instâncias.
Como o Garbage Collector precisa percorrer todas as listas com referências em uso pela aplicação e isso demanda tempo e processamento (a nível de programação), o GC é agendado para ser executado, esporadicamente, em intervalos de tempos não definidos. Não deve-se esperar, portanto, que o Garbage Collector destruirá as instâncias imediatamente assim que todas as referências para a mesma são removidas. Pois o GC não "varre" a aplicação todo o instante.
O programador não tem como prever quando o Garbage Collector passará "recolhendo o lixo" diz Alex Harui, que numa outra brincadeira, afirma que o "homem do lixo" só dá as caras às sextas-feiras de manhã (o autor é responsável por uma teoria pessoal de funcionamento do Garbage Collector que diz que o GC é acionado após algumas alocações de memória. Para quem quiser lêr, existe uma apresentação em Powerpoint do Alex Harui em http://blogs.adobe.com/aharui/GarbageCollection/GCAtomic.ppt).
O método Mark and Sweep também gera alguns problemas:
Supondo que uma instância chamada inst seja adicionada à lista de visualização (display list) da aplicação e que um evento do sistema seja registrado:
systemManager.addEventListener("mouseMove", inst.onMouseMove);
addChild(inst);
Mesmo que a instância seja removida da lista de visualização da aplicação, se o evento não for removido, o systemManager manterá a referência da instância inst consigo e portanto, fará com que o Garbage Collector, método Mark and Sweep, falhe. Ou seja, a instância nunca será destruída por nenhuma técnica empregada pelo Garbage Collector.
Para mais detalhes e prevenção de problemas relacionados à gerenciamento de memória no Actionscript 3, ver artigo em inglês: Estratégias para manuseamento de memória no Flash Player 9.
Fontes
- Entendendo o Garbage Collector no Flash Player 9 (Understanding garbage collector in Flash Player 9), Grant Skinner. Endereço: http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html
- Garbage Collector e problemas de memória (Garbage Collector and Memory Leaks), Alex Harui. Endereço: http://blogs.adobe.com/aharui/2007/03/garbage_collection_and_memory.html
- Mark-and-Sweep Garbage Collection, Bruno R. Preiss. Endereço: http://www.brpreiss.com/books/opus5/html/page424.html
- Mark and Sweep Garbage Collection, Leonidas Fegaras. Endereço: http://lambda.uta.edu/cse5317/notes/node47.html
- Garbage Collection (compute science), Wikipedia. Endereço: http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)
- Estratégias para manuseamento de memória no Flash Player 9 (Resource management strategies in Flash Player 9), Grant Skinner. Endereço: http://www.adobe.com/devnet/flashplayer/articles/resource_management.html




