Registrei o seguinte bug do Chrome , o que levou a muitos vazamentos de memória sérios e não óbvios no meu código:
(Esses resultados usam o criador de perfil de memória das Ferramentas de Desenvolvimento do Chrome , que executa o GC e, em seguida, tira um instantâneo de tudo o que não é coletado em lixo.)
No código abaixo, a someClass
instância é coletada como lixo (boa):
var someClass = function() {};
function f() {
var some = new someClass();
return function() {};
}
window.f_ = f();
Mas não será coletado lixo neste caso (ruim):
var someClass = function() {};
function f() {
var some = new someClass();
function unreachable() { some; }
return function() {};
}
window.f_ = f();
E a captura de tela correspondente:
Parece que um fechamento (neste caso function() {}
) mantém todos os objetos "vivos" se o objeto for referenciado por qualquer outro fechamento no mesmo contexto, independentemente de esse fechamento ser ou não alcançável.
Minha pergunta é sobre a coleta de lixo de fechamento em outros navegadores (IE 9+ e Firefox). Estou familiarizado com as ferramentas do webkit, como o criador de perfil de heap JavaScript, mas conheço pouco das ferramentas de outros navegadores, portanto não pude testar isso.
Em qual desses três casos o IE9 + e o Firefox coletam a someClass
instância?
unreachable
função nunca é executada, portanto nada é realmente registrado.Respostas:
Até onde eu sei, isso não é um bug, mas o comportamento esperado.
Na página de gerenciamento de memória da Mozilla : "A partir de 2012, todos os navegadores modernos lançam um coletor de lixo de marcação e varredura". "Limitação: os objetos precisam ser explicitamente inacessíveis " .
Nos seus exemplos em que a falha
some
ainda é alcançável no fechamento. Eu tentei duas maneiras de torná-lo inacessível e ambos funcionam. Ou você definesome=null
quando não precisa mais, ou definewindow.f_ = null;
e desaparece.Atualizar
Eu tentei no Chrome 30, FF25, Opera 12 e IE10 no Windows.
O padrão não diz nada sobre a coleta de lixo, mas fornece algumas pistas do que deve acontecer.
Portanto, uma função terá acesso ao ambiente do pai.
Portanto,
some
deve estar disponível no fechamento da função de retorno.Então, por que nem sempre está disponível?
Parece que o Chrome e o FF são inteligentes o suficiente para eliminar a variável em alguns casos, mas no Opera e no IE a
some
variável está disponível no fechamento (NB: para visualizar esse conjunto, um ponto de interrupçãoreturn null
e verificar o depurador).O GC pode ser aprimorado para detectar se
some
é usado ou não nas funções, mas será complicado.Um mau exemplo:
No exemplo acima, o GC não tem como saber se a variável é usada ou não (código testado e funciona no Chrome30, FF25, Opera 12 e IE10).
A memória será liberada se a referência ao objeto for quebrada, atribuindo outro valor a
window.f_
.Na minha opinião, isso não é um bug.
fonte
setTimeout()
retorno de chamada é executado, o escopo da função dosetTimeout()
retorno de chamada é concluído e todo o escopo deve ser coletado como lixo, liberando sua referência asome
. Não há mais nenhum código que possa ser executado que possa alcançar a instância dosome
encerramento. Deve ser recolhido lixo. O último exemplo é ainda pior, porqueunreachable()
nem é chamado e ninguém faz referência a ele. Seu escopo também deve ser analisado. Ambos parecem bugs. Não há requisito de linguagem no JS para "liberar" coisas em um escopo de função.f()
é chamado, não há mais referências reaissome
. É inacessível e deve ser GCed.eval
é um caso realmente especial. Por exemplo,eval
não pode ser alternativo ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), por exemplovar eval2 = eval
. Seeval
for usado (e como não pode ser chamado por um nome diferente, é fácil), devemos assumir que ele pode usar qualquer coisa no escopo.Eu testei isso no IE9 + e no Firefox.
Site ao vivo aqui .
Eu esperava terminar com uma matriz de 500
function() {}
's, usando memória mínima.Infelizmente, não foi esse o caso. Cada função vazia mantém uma matriz (para sempre inacessível, mas não GC'ed) de um milhão de números.
O Chrome finalmente pára e morre, o Firefox termina tudo depois de usar quase 4 GB de RAM, e o IE fica assintoticamente mais lento até mostrar "Memória insuficiente".
A remoção de qualquer uma das linhas comentadas corrige tudo.
Parece que todos esses três navegadores (Chrome, Firefox e IE) mantêm um registro de ambiente por contexto, não por fechamento. Boris supõe que a razão por trás dessa decisão é o desempenho, e isso parece provável, embora não tenha certeza do desempenho que pode ser chamado à luz do experimento acima.
Se for necessário referenciar o fechamento
some
(concedido, não o usei aqui, mas imagine que sim), se em vez deeu uso
ele corrigirá os problemas de memória movendo o fechamento para um contexto diferente da minha outra função.
Isso tornará minha vida muito mais entediante.
PS Por curiosidade, tentei isso em Java (usando sua capacidade de definir classes dentro de funções). O GC funciona como eu esperava originalmente para Javascript.
fonte
As heurísticas variam, mas uma maneira comum de implementar esse tipo de coisa é criar um registro de ambiente para cada chamada
f()
no seu caso e armazenar apenas os locaisf
que estão realmente fechados (por algum fechamento) nesse registro de ambiente. Em seguida, qualquer fechamento criado na chamada paraf
manter vivo o registro do ambiente. Acredito que é assim que o Firefox implementa fechamentos, pelo menos.Isso tem os benefícios de acesso rápido a variáveis fechadas e simplicidade de implementação. Ele tem a desvantagem do efeito observado, onde um fechamento de curta duração que fecha sobre algumas variáveis faz com que seja mantido vivo por fechamentos de longa duração.
Pode-se tentar criar vários registros de ambiente para diferentes fechamentos, dependendo do que eles realmente fecham, mas isso pode ficar muito complicado muito rapidamente e pode causar problemas de desempenho e memória por conta própria ...
fonte
O(n^2)
ouO(2^n)
como uma explosão, mas não um aumento proporcional.como add (5); // retorna 5
adicione (20); // retorna 25 (5 + 20)
adicione (3); // retorna 28 (25 + 3)
A maneira como você pode fazer isso primeiro é normal. Defina uma variável global. É claro que você pode usar uma variável global para armazenar o total. Mas lembre-se de que esse cara o comerá vivo se você (ab) usar globals.
agora mais recente maneira usando o fechamento sem definir variável global
fonte
fonte
fonte