A coleta de lixo é um mecanismo oculto do JavaScript
. Geralmente não precisamos nos preocupar com a coleta de lixo, só precisamos nos concentrar no desenvolvimento de funções. Mas isso não significa que podemos sentar e relaxar ao escrever JavaScript
. À medida que as funções que implementamos se tornam cada vez mais complexas e a quantidade de código se acumula, os problemas de desempenho tornam-se cada vez mais proeminentes. Como escrever código que seja executado mais rapidamente e ocupe menos memória é a busca incessante dos programadores. Um excelente programador sempre pode alcançar resultados surpreendentes com recursos extremamente limitados. Essa também é a diferença entre seres comuns e deuses distantes.
? Ao executar na memória do computador, todas as variáveis, objetos e funções que definimos no código ocuparão uma certa quantidade de espaço de memória na memória. Nos computadores, o espaço de memória é um recurso muito escasso. Devemos sempre estar atentos ao uso da memória. Afinal, os módulos de memória são muito caros! Uma variável, função ou objeto pode ser chamado de lixo se não for mais necessário para a execução subsequente do código após a criação.
Embora seja muito fácil entender intuitivamente a definição de lixo, para um programa de computador, é difícil concluirmos em um determinado momento que as variáveis, funções ou objetos atualmente existentes não serão mais utilizados no futuro. Para reduzir o custo da memória do computador e garantir a execução normal dos programas de computador, normalmente estipulamos que objetos ou variáveis que atendam a qualquer uma das seguintes condições são lixo:
variáveis ou objetos que não são referenciados equivalem a uma casa sem porta, nunca podemos entrar, por isso é impossível utilizá-los. Embora os objetos inacessíveis estejam conectados, eles ainda são inacessíveis do lado de fora e, portanto, não podem ser usados novamente. Objetos ou variáveis que atendam às condições acima nunca mais serão utilizados na execução futura do programa, portanto podem ser tratados com segurança como coleta de lixo.
Quando esclarecemos os objetos que precisam ser descartados através da definição acima, isso significa que não há lixo nas variáveis e objetos restantes?
Não! O lixo que identificamos atualmente é apenas uma parte de todo o lixo. Ainda haverá outro lixo que não atenda às condições acima, mas não será reaproveitado.
Pode-se dizer que o lixo que atende à definição acima é “lixo absoluto” e outro lixo escondido no programa é “lixo relativo”?
O mecanismo de coleta de lixo ( GC,Garbage Collection
) é responsável por reciclar variáveis inúteis e espaço de memória ocupado durante a execução do programa. O fenômeno de um objeto ainda existir na memória mesmo não tendo possibilidade de ser usado novamente é chamado de vazamento de memória . Vazamentos de memória são um fenômeno muito perigoso, especialmente em programas de longa duração. Se um programa apresentar vazamento de memória, ele ocupará cada vez mais espaço de memória até ficar sem memória.
Strings, objetos e arrays não têm tamanho fixo, portanto, a alocação dinâmica de armazenamento para eles só pode ser feita se seu tamanho for conhecido. Cada vez que um programa JavaScript cria uma string, array ou objeto, o interpretador aloca memória para armazenar a entidade. Sempre que a memória é alocada dinamicamente dessa forma, ela deve eventualmente ser liberada para que possa ser usada novamente, caso contrário, o interpretador JavaScript consumirá toda a memória disponível no sistema, causando o travamento do sistema;
O mecanismo de coleta de lixo do JavaScript
verificará intermitentemente variáveis e objetos inúteis (lixo) e liberará o espaço que eles ocupam.
Diferentes linguagens de programação adotam diferentes estratégias de coleta de lixo. Por exemplo, C++
não possui um mecanismo de coleta de lixo. Todo o gerenciamento de memória depende das habilidades do próprio programador, o que torna C++
mais difícil de dominar. JavaScript
usa acessibilidade para gerenciar memória. Literalmente, acessibilidade significa alcançável, o que significa que o programa pode acessar e usar variáveis e objetos de alguma forma.
JavaScript
especifica um conjunto inerente de valores alcançáveis, e os valores no conjunto são inerentemente alcançáveis:
as variáveis acima são chamadas de raízes , que são os nós superiores da árvore de acessibilidade;
Uma variável ou objeto é considerado alcançável se for usado direta ou indiretamente pela variável raiz.
Em outras palavras, um valor é alcançável se puder ser alcançado através da raiz (por exemplo, Abcde
).
let people = { meninos:{ meninos1:{nome:'xiaoming'}, meninos2:{nome:'xiaojun'}, }, garotas:{ meninas1:{nome:'xiaohong'}, meninas2:{nome:'huahua'}, }};
O código acima cria um objeto e o atribui à variável people
. A variável people
contém dois objetos, boys
e girls
, e boys
e girls
contêm dois subobjetos respectivamente. Isso também cria uma estrutura de dados contendo 3
níveis de relacionamentos de referência (independentemente do tipo básico de dados), conforme mostrado abaixo:
Entre eles, o nó people
é naturalmente acessível por ser uma variável global. Os nós boys
e girls
são indiretamente acessíveis porque são diretamente referenciados por variáveis globais. boys1
, boys2
, girls1
e girls2
também são variáveis acessíveis porque são usadas indiretamente por variáveis globais e podem ser acessadas através de people.boys.boys
.
Se adicionarmos o seguinte código após o código acima:
people.girls.girls2 = null; people.girls.girls1 = people.boys.boys2
Então, o diagrama de hierarquia de referência acima ficará como segue:
Dentre eles, girls1
e girls2
tornaram-se nós inacessíveis devido à desconexão do nó grils
, o que significa que serão reciclados pelo mecanismo de coleta de lixo.
E se neste momento executarmos o seguinte código:
people.boys.boys2 = null
então o diagrama de hierarquia de referência ficará assim:
Neste momento, embora o nó boys
e o nó boys2
estejam desconectados, devido ao relacionamento de referência entre boys2
e girls
, boys2
ainda está acessível e não será reciclado pelo mecanismo de coleta de lixo.
O diagrama de associação acima prova porque o valor equivalente da variável global é chamado de root , pois no diagrama de associação esse tipo de valor geralmente aparece como o nó raiz da árvore de relacionamento.
deixe as pessoas = { meninos:{ meninos1:{nome:'xiaoming'}, meninos2:{nome:'xiaojun'}, }, garotas:{ meninas1:{nome:'xiaohong'}, meninas2:{nome:'huahua'}, }};pessoas.boys.boys2.girlfriend = pessoas.girls.girls1; //boys2 refere-se a girls1people.girls.girls1.boyfriend = people.boys.boys2; //girls1 refere-se a boys2.
O código acima cria um relacionamento inter-relacionado entre boys2
e girls1
.
Neste ponto, se cortarmos a associação entre boys
e boys2
:
delete people.boys.boys2
o diagrama de associação entre objetos é o seguinte:
Obviamente, não existem nós inacessíveis.
Neste ponto, se cortarmos a conexão do relacionamento boyfriend
:
people.girls.girls1
;
Neste momento, embora ainda exista um relacionamento girlfriend
entre boys2
e girls1
, boys2
se tornará um nó inacessível e será recuperado pelo mecanismo de coleta de lixo.
deixe as pessoas = { meninos:{ meninos1:{nome:'xiaoming'}, meninos2:{nome:'xiaojun'}, }, garotas:{ meninas1:{nome:'xiaohong'}, meninas2:{nome:'huahua'}, }};delete people.boys;delete people.girls;
O diagrama de hierarquia de referência formado pelo código acima é o seguinte:
Neste momento, embora ainda exista uma relação de referência mútua entre os objetos dentro da caixa pontilhada, esses objetos também estão inacessíveis e serão excluídos pelo mecanismo de coleta de lixo. Esses nós perderam o relacionamento com a raiz e tornaram-se inacessíveis.
A chamada contagem de referência, como o nome sugere, é contada toda vez que um objeto é referenciado. Adicionar uma referência irá aumentá-la em um, e excluir uma referência irá diminuí-la em um. número torna-se 0, é considerado Lixo, excluindo assim objetos para recuperar memória.
Por exemplo:
deixe usuário = {nomedeusuário:'xiaoming'}; //O objeto é referenciado pela variável do usuário, conte +1 deixe usuário2 = usuário; //O objeto é referenciado por uma nova variável e a contagem +1 usuário = nulo; //A variável não se refere mais ao objeto, a contagem é -1 usuário2 = nulo; //A variável não se refere mais ao objeto, número ímpar -1 //Neste momento, o número de referências de objetos é 0 e será excluído.
Embora o método de contagem de referências pareça muito razoável, na verdade, existem lacunas óbvias no mecanismo de reciclagem de memória usando o método de contagem de referências.
Por exemplo:
deixe menino = {}; deixe garota = {}; menino.namorada = menina; menina.namorado = menino; menino = nulo; girl = null;
O código acima possui referências mútuas entre boy
e girl
. A contagem exclui as referências em boy
e girl
, e os dois objetos não serão reciclados. Devido à existência de referências circulares, as contagens de referência dos dois objetos anônimos nunca retornarão a zero, resultando em vazamento de memória.
Existe um conceito de ponteiro inteligente ( shared_ptr
) em C++
. Os programadores podem usar o ponteiro inteligente para usar o destruidor de objetos para liberar a contagem de referência. No entanto, ocorrerão vazamentos de memória no caso de referências circulares.
Felizmente, JavaScript
adotou outra estratégia mais segura, que evita ainda mais o risco de vazamentos de memória.
Marcar mark and sweep
é um algoritmo de coleta de lixo adotado pelo mecanismo JavaScript
. Seu princípio básico é começar da raiz , percorrer primeiro o relacionamento de referência entre as variáveis e colocar uma marca (优秀员工徽章
) nas variáveis percorridas. Os objetos não marcados são finalmente excluídos.
O processo básico do algoritmo é o seguinte:
2
até que não haja novos funcionários excelentes,Por exemplo:
Se houver um relacionamento de referência de objeto em nosso programa conforme mostrado abaixo:
Podemos ver claramente que existe uma “ilha alcançável” no lado direito de toda a imagem. A partir da raiz , a ilha nunca pode ser alcançada. Mas o coletor de lixo não tem a perspectiva de Deus como a nossa. Eles apenas marcarão o nó raiz como um funcionário excelente com base no algoritmo.
Em seguida, comece pelos funcionários destacados e encontre todos os nós citados pelos funcionários destacados, como os três nós na caixa pontilhada na figura acima. Em seguida, marque os nós recém-encontrados como funcionários excelentes.
O processo de localização e marcação é repetido até que todos os nós que podem ser encontrados sejam marcados com sucesso.
Finalmente, o efeito mostrado na figura abaixo é alcançado:
Como as ilhas à direita ainda estão desmarcadas após o término do ciclo de execução do algoritmo, esses nós não poderão ser alcançados pela tarefa do coletor de lixo e eventualmente serão limpos.
As crianças que estudaram estruturas de dados e algoritmos podem se surpreender ao descobrir que se trata de travessia de grafos, semelhante aos algoritmos de grafos conectados.
A coleta de lixo é uma tarefa em grande escala. Especialmente quando a quantidade de código é muito grande, a execução frequente do algoritmo de coleta de lixo reduzirá significativamente a execução do programa. O algoritmo JavaScript
fez muitas otimizações na coleta de lixo para garantir que o programa possa ser executado com eficiência e ao mesmo tempo garantir a execução normal do trabalho de reciclagem.
As estratégias adotadas para otimização de desempenho geralmente incluem os seguintes pontos:
Os programas JavaScript
manterão um número considerável de variáveis durante a execução, e a varredura frequente dessas variáveis causará sobrecarga significativa. No entanto, essas variáveis têm características próprias no ciclo de vida. Por exemplo, as variáveis locais são frequentemente criadas, usadas rapidamente e depois descartadas, enquanto as variáveis globais ocupam a memória por muito tempo. JavaScript
gerencia os dois tipos de objetos separadamente. Para variáveis locais que são rapidamente criadas, usadas e descartadas, o coletor de lixo fará a varredura com frequência para garantir que essas variáveis sejam rapidamente limpas após perderem seu uso. Para variáveis que retêm memória por muito tempo, reduza a frequência de verificá-las, economizando assim uma certa quantidade de sobrecarga.
A ideia incremental é muito comum na otimização de desempenho e também pode ser usada para coleta de lixo. Quando o número de variáveis é muito grande, é obviamente muito demorado percorrer todas as variáveis de uma vez e emitir notas pendentes aos funcionários, resultando em atrasos durante a execução do programa. Portanto, o mecanismo dividirá o trabalho de coleta de lixo em várias subtarefas e executará gradualmente cada pequena tarefa durante a execução do programa. Isso causará um certo atraso na recuperação, mas geralmente não causará atrasos óbvios no programa.
CPU
sempre funciona, mesmo em programas complexos. Isso ocorre principalmente porque CPU
funciona muito CPU
e IO
periférica costuma ser várias ordens de magnitude mais lenta. inativo Este é um método de otimização de desempenho muito eficaz e basicamente não terá nenhum efeito adverso no programa em si. Essa estratégia é semelhante à atualização do tempo ocioso do sistema e os usuários não têm conhecimento da execução em segundo plano.
A principal tarefa deste artigo é simplesmente encerrar os mecanismos de coleta de lixo, estratégias e métodos de otimização comumente usados. Não se destina a fornecer a todos uma compreensão profunda dos princípios de execução em segundo plano do mecanismo.
Através deste artigo, você deve entender:
JavaScript
, que é executado em segundo plano e não exige que nos preocupemos com isso.