[JVM]-[Compreensão aprofundada das notas de estudo da máquina virtual Java]-Capítulo 3-Coletor de lixo e estratégia de alocação de memória

Determine se o objeto está vivo

Algoritmo de contagem de referência


Adicione um contador de referência ao objeto. Sempre que houver uma referência a ele , o valor do contador será incrementado em um; quando a referência expirar, o valor do contador será decrementado em um; um objeto com contador zero em nenhum momento poderá ser usado novamente. É difícil para o algoritmo de contagem de referências resolver o problema de referências circulares entre objetos , ou seja, dois objetos se referem um ao outro, mas esses dois objetos não serão mais usados ​​em outro lugar. Neste momento, os dois objetos devem ser reciclado, mas como o número de referência não é 0, não pode ser reciclado. Além disso, como é necessário definir um contador para cada objeto, se houver muitos objetos, o número de contadores também será grande e o espaço ocupado também será muito considerável.

Algoritmo de análise de acessibilidade

Uma série de objetos raiz chamados GC Roots são usados ​​como conjunto de nós iniciais. A partir desses nós, pesquise para baixo de acordo com o relacionamento de referência. O caminho percorrido pelo processo de pesquisa é chamado de cadeia de referência . Se não houver referência entre um objeto e GC Roots Quando a cadeia está conectada, ou quando o objeto está inacessível pelo GC Roots, significa que o objeto não pode mais ser usado

Realmente declarar um objeto morto requer pelo menos dois processos de marcação: se o objeto for encontrado após a análise de alcançabilidade que não há nenhuma cadeia de referência conectada ao GC Roots, então ele será marcado pela primeira vez; então um processo de filtragem é executado, e o As condições de filtragem são se é necessário que execute o método finalize() . Se o objeto não cobre o método finalize() ou se seu método finalize() foi chamado pela máquina virtual , a máquina virtual pensa que não é necessário executá-lo e marcá-lo pela segunda vez.

Portanto, o objeto pode salvar a si mesmo : primeiro, substitua o método finalize() e atribua-se (isto é, isto) a qualquer objeto na cadeia de referência durante a execução. Como o método finalize() será executado no máximo uma vez, esse autorresgate só pode ser executado uma vez, mas não é recomendado usar o método finalize().

Objetos corrigidos disponíveis como GC Roots

  1. Objetos referenciados na pilha da máquina virtual (tabela de variáveis ​​locais no quadro da pilha), como parâmetros, variáveis ​​locais, variáveis ​​temporárias, etc. usados ​​na pilha de métodos chamada por cada thread
  2. Objetos referenciados na pilha de métodos nativos JNI (comumente conhecidos como métodos locais nativos)
  3. Objetos referenciados por propriedades estáticas de classe na área de método, como variáveis ​​estáticas de tipo de referência de classes Java
  4. Objetos referenciados por constantes na área de métodos, como referências no conjunto de constantes de string String Table
  5. Todos os objetos mantidos por bloqueios sincronizados (palavra-chave sincronizada)
  6. JMXBean que reflete a situação interna da máquina virtual Java, retornos de chamada registrados em JVMTI, cache de código local, etc.

Como um objeto do GC Roots, ele deve estar em uso. Assim, podemos pensar nos objetos referenciados na pilha de métodos. A pilha de métodos inclui naturalmente métodos comuns e métodos locais; variáveis ​​e constantes estáticas de classe disponíveis globalmente. O objeto preso pela fechadura não só está sendo usado, mas também não permite que morra à vontade.

Sobre citações

Após o JDK 1.2, o conceito de referência foi ampliado e dividido em quatro tipos:

  1. Referência ForteReferência ForteReferência ForteReferência forte : A definição tradicional de "referência" é a atribuição de referência onipresente no código, ou seja, um relacionamento de referência como "Object obj = new Object()" . Em qualquer caso , contanto que pois existe uma forte
    relação de referência. Se ainda existir, o colecionador nunca irá recuperá-lo.
  2. Referência Suave Referência SuaveSoftR e f e r e n ce : Objetos que estão associados apenas a referências suaves. Antes que o sistema esteja prestes a experimentar uma exceção de estouro de memória, esses objetos serão incluídos no escopo de reciclagem para a segunda reciclagem. Se essa reciclagem não tiver ocorrido ainda assim, memória suficiente gerará uma exceção de estouro de memória.
  3. 弱引用 W eak R eference Referência fracaReferência fraca : A força é mais fraca do que a referência suave. O objeto associado à referência fraca só pode sobreviver até que ocorra a próxima coleta de lixo . Quando o coletor de lixo começar a funcionar, independentemente de a memória atual ser suficiente, os objetos associados apenas a referências fracas serão reciclados.
  4. Referência Fantasma Referência FantasmaReferência fantasma : também conhecida como "referência fantasma" ou "referência fantasma".Se um objeto tem uma referência virtual não afetará em nada seu tempo de sobrevivência , e é impossível obter um objeto através uma referência virtual.Instância de objeto. O único propósito de definir uma referência virtual para um objeto é receber uma notificação do sistema quando o objeto for reciclado.

Após o JDK 1.2, as classes SoftReference, WeakReference e PhantomReference são fornecidas para implementar referências suaves, referências fracas e referências virtuais.

Algoritmo de coleta de lixo rastreando coleta de lixo

Depois de saber que tipo de objetos pertencem aos objetos a serem reciclados, o próximo passo é considerar como reciclar os objetos.

De acordo com o algoritmo para determinar se um objeto está ativo, os algoritmos de coleta de lixo podem ser divididos em contagem de referência, coleta de lixo e rastreamento de coleta de lixo . Como o algoritmo de análise de acessibilidade é usado na JVM, apenas o algoritmo de coleta de lixo de rastreamento será discutido aqui.

teoria da coleção geracional

Muitos princípios de design do coletor de lixo são baseados na teoria da coleta geracional:

  1. Hipótese de geração fraca : a maioria dos objetos nasce e morre
  2. Hipótese de Geração Forte : Quanto mais vezes sobreviver ao processo de coleta de lixo, mais difícil será morrer.
  3. Hipótese de citação entre gerações : as citações entre gerações representam apenas um número muito pequeno em comparação com as citações entre gerações

A primeira e a segunda hipóteses nos dizem: o coletor deve dividir o heap Java em diferentes áreas , depois alocar os objetos reciclados em diferentes áreas para armazenamento de acordo com sua idade e adotar diferentes estratégias de coleta com base em suas diferentes características de sobrevivência.

De acordo com diferentes características de sobrevivência, o heap Java pode pelo menos ser dividido em duas áreas: a nova geração e a velha geração.A nova geração corresponde a objetos que "vivem e morrem", e a velha geração corresponde a objetos que são "difíceis morrer".
Coleção de acordo com diferentes áreas As operações incluem Minor GC (coleção para a nova geração), Major GC (coleção para a geração antiga), Full GC (coleção para toda a área de heap e método Java), etc.
Diferentes algoritmos são adotado para diferentes áreas, como algoritmos mark-and-clear. , algoritmo mark-copy , algoritmo mark-collation

Em suma, tudo decorre da teoria da coleção geracional

Um problema óbvio com a coleção geracional é o problema das referências entre gerações entre objetos : é inteiramente possível que os objetos da nova geração sejam referenciados pela geração antiga. Para encontrar os objetos sobreviventes nesta área, temos que usar Raízes GC fixas. Além disso, todos os objetos em toda a geração antiga são percorridos adicionalmente para garantir a exatidão do algoritmo de análise de acessibilidade. Atravessar os objetos em toda a geração antiga é, sem dúvida, uma enorme carga de desempenho.

Isto leva à terceira regra prática mencionada acima. Esta regra é, na verdade, baseada nos dois primeiros raciocínios: dois objetos que fazem referência um ao outro devem tender a sobreviver ou morrer ao mesmo tempo. Por exemplo, se um objeto de nova geração tiver uma referência de geração cruzada, então, como o objeto de geração antiga é difícil de morrer, o objeto de nova geração continuará a sobreviver.Então, à medida que a idade aumenta gradualmente, o objeto de nova geração também se tornará a geração antiga. Neste momento, o objeto da nova geração se tornará um objeto da geração antiga. As referências de geração também são eliminadas

Portanto, não há necessidade de percorrer toda a geração antiga para obter um pequeno número de referências intergeracionais. Basta criar uma estrutura de dados global na nova geraçãoconjunto de memória(Remember Set), esta estrutura divide a geração antiga em vários pequenos blocos e, em seguida, identifica quais blocos terão referências intergeracionais. Ao executar Minor GC posteriormente, você só precisa adicionar os objetos na área de geração antiga marcada com referências entre gerações na memória definida como GC Roots.

Embora o método de uso do conjunto de memória exija a manutenção dos dados registrados quando o relacionamento de referência do objeto muda, ele ainda é econômico em comparação com o custo de percorrer todo o objeto da geração antiga.

algoritmo de coleta de lixo

algoritmo de varredura de marca

Algoritmo : Primeiro marque todos os objetos que precisam ser reciclados e, após a conclusão da marcação, os objetos marcados serão reciclados uniformemente; também pode ser revertido, marcando todos os objetos sobreviventes e reciclando todos os objetos não marcados.

Desvantagens :

  • A eficiência de execução é instável : se o heap Java contiver um grande número de objetos e a maioria deles precisar ser reciclada , serão necessárias mais ações de marcação e limpeza, fazendo com que a eficiência de execução dos processos de marcação e limpeza varie com os objetos . aumento no número de
  • Fragmentação do espaço de memória : Após a limpeza da marca, um grande número de fragmentos de memória descontínuos será gerado. Muitos fragmentos de espaço podem fazer com que espaço de memória contínua insuficiente seja encontrado quando objetos maiores precisarem ser alocados no futuro e uma coleta de lixo deve ser acionada antecipadamente.
    algoritmo de varredura de marca

algoritmo de marcação-cópia

duplicação de meia região

A fim de resolver o problema da eficiência de execução instável diante de um grande número de objetos recicláveis ​​no algoritmo mark-sweep , a propostaduplicação de meia regiãoAlgoritmo: Divida a memória disponível do heap Java em dois blocos de tamanho igual e use apenas um deles por vez. Quando a memória do bloco usado se esgotar, copie os objetos sobreviventes para outro bloco e, em seguida, copie o objetos restantes para outro bloco. Limpar
de uma vez é equivalente a limpá-lo quando metade do espaço estiver cheio, em vez de esperar até que todo o espaço esteja cheio. Dessa forma, menos objetos precisam ser reciclados cada vez que ele é resolvido.O problema da eficiência de execução também foi atenuado até certo ponto.

Se a maioria dos objetos na memória sobreviver, uma grande quantidade de sobrecarga de cópia entre memórias será gerada; e quando a maioria dos objetos na memória for reciclada, a sobrecarga necessária para copiar os objetos sobreviventes será muito pequena, então o algoritmo é mais adequado para a nova geração, e quando os objetos sobreviventes em uma metade da área são copiados para a outra metade da área, eles podem ser colocados de maneira regular e ordenada sem o problema de fragmentação do espaço .

Desvantagens : A memória disponível é reduzida à metade do original e o espaço é muito desperdiçado
algoritmo de marcação-cópia

Reciclagem de aplicativos

A reciclagem estilo Appel é uma estratégia de geração de cópias de meia área mais otimizada, que será discutida mais adianteSerial,ParNovoTodos os colecionadores da nova geração adotam esta estratégia.

Método específico : divida a nova geração em um espaço Eden maior e dois espaços Survivor menores ( de Survivor e To Survivor respectivamente ). Use apenas Eden e From Survivor de cada vez para alocar memória. Quando a coleta de lixo ocorrer, Eden será usado. Copie o objetos sobreviventes no espaço Do Sobrevivente para outro Espaço Para o Sobrevivente de uma só vez e, em seguida, limpe os espaços Éden e Do Sobrevivente diretamente.
A proporção padrão de tamanho Eden e Survivor da máquina virtual HotSpot é 8:1 , ou seja, o espaço de memória disponível em cada nova geração é 90% de toda a capacidade da nova geração, o que resolve o problema de desperdício excessivo de cópia de meia área espaço.

Embora existam muito poucos objetos sobreviventes na nova geração, não há como garantir 100% que não mais do que 10% dos objetos sobreviverão a cada coleção. Uma vez que os objetos sobreviventes excedam 10%, o restante do To Survivor não sobreviverá. ser suficiente para armazenar Esses objetos
precisam então ser alocados em um mecanismo de garantia para resolver este problema: quando não há espaço suficiente no To Survivor para armazenar os objetos que sobreviveram ao último GC Menor, os objetos que não podem ser acomodados serão inseridos diretamente no a velha geração.

Na maioria dos casos, os objetos são alocados no Eden na nova geração.Quando a área Eden não tiver espaço suficiente para alocação, a máquina virtual iniciará um Minor GC.

Por que existem dois sobreviventes?

Dois Sobreviventes resolvem o problema da fragmentação da memória.
Suponha que haja apenas um Sobrevivente e todos os objetos sobreviventes no Éden sejam colocados neste Sobrevivente. Na próxima marcação, os objetos sobreviventes no Éden ainda podem ser copiados ordenadamente para o Sobrevivente para armazenamento. No entanto, alguns dos objetos originalmente armazenados no Sobrevivente podem ser reciclados e alguns sobreviverão, mas não há lugar para copiar e armazenar. Essas partes . Os objetos sobreviventes, porque não há outros sobreviventes, terão o problema de fragmentação da memória.
Em outras palavras, enquanto houver objetos armazenados em uma área, a fragmentação da memória ocorrerá inevitavelmente nesta área . A solução é introduzir uma área.Está sempre vazia e não armazena objetos.

Portanto, são necessários dois Sobreviventes, um do Sobrevivente e um do Sobrevivente. Cada vez que o objeto é armazenado, ele é armazenado no Éden e no De. Quando o lixo é coletado, os objetos sobreviventes no Éden e no De são cuidadosamente copiados para o Para para armazenamento. Recicle tudo com De e depois troque identidades com Para

Isso garante que To Survivor esteja sempre vazio e possa ser usado para copiar e classificar os objetos sobreviventes em Eden e From, o que resolve o problema de fragmentação do espaço

E por que não mais sobreviventes? Quanto mais espaço é dividido, menor é o espaço de cada Sobrevivente e mais fácil é preencher o espaço, e é impossível prever o número de objetos que podem sobreviver a cada vez. Quando há muitos objetos sobreviventes, é é muito pequeno. O Sobrevivente não pode ser armazenado. E dois Sobreviventes são suficientes para resolver o problema, não há necessidade de continuar aumentando o número de Sobreviventes

Por que Eden e Survivor precisam de 8:1:1?

Em primeiro lugar, é principalmente a teoria da geração fraca: a maioria dos objetos será reciclada, portanto o espaço do Survivor não precisa ser tão grande. Após uma série de testes de desempenho, constatou-se que esta relação era a melhor, por isso esta relação foi selecionada.

Algoritmo de agrupamento de marcas

O algoritmo de marcação-cópia é principalmente para a nova geração, porque quando a taxa de sobrevivência do objeto é alta , muitas operações de cópia são necessárias e a eficiência será reduzida . Mais importante ainda, o espaço usado para salvar os objetos sobreviventes é relativamente pequeno. , se a taxa de sobrevivência do objeto for alta durante a maioria das coletas de lixo, será necessário espaço adicional para garantias de alocação

O algoritmo mark-sort foi projetado para as características de morte de objetos na velhice. O algoritmo é: o processo de marcação ainda é o mesmo do "algoritmo de limpeza de marca", mas o próximo passo não é limpar diretamente os objetos recicláveis, mas mover todos os objetos sobreviventes para o final do espaço de memória, e então limpe diretamente a memória fora do limite, para que não haja problemas de fragmentação de memória.

A diferença essencial entre o algoritmo de varredura de marcas e o algoritmo de agrupamento de marcas é que o primeiro é um algoritmo de reciclagem imóvel, enquanto o último é um algoritmo de reciclagem móvel.

Detalhes de implementação do algoritmo HotSpot

Enumeração do nó raiz

O thread do usuário deve ser pausado durante a etapa de enumeração do nó raiz, o que causará problemas de "Stop The World" semelhantes aos da desfragmentação de memória anterior. Deve ser realizado em um instantâneo do espaço do sistema que possa garantir consistência, garantindo que o relacionamento de referência do objeto do conjunto de nós raiz não será alterado durante o processo de análise, a fim de garantir a precisão dos resultados da análise.

Atualmente, as JVMs convencionais usamGerenciamento preciso de memória, ou seja, saber exatamente que tipo de dado está em um determinado local da memória, por exemplo, se um determinado número inteiro é um tipo de referência que aponta para um determinado endereço de memória ou apenas um número inteiro simples.

Portanto, a máquina virtual deve ter uma maneira de obter diretamente onde as referências dos objetos estão armazenadas. O HotSpot usa um conjunto de estruturas de dados chamado OopMap para atingir esse propósito, que será discutido emlocalização específica(Ou seja, o ponto de segurança abaixo) Registre quais locais na pilha e nos registradores são referências. Desta forma, o coletor pode saber essas informações diretamente durante a varredura, não precisando iniciar a busca na área de métodos e outras raízes do GC.

local seguro

Com a ajuda do OopMap, o HotSpot pode concluir a enumeração do GC Roots com rapidez e precisão

Porém, existem muitas instruções que fazem com que o relacionamento de referência mude, ou seja, o conteúdo do OopMap muda. Se um OopMap correspondente for gerado para cada instrução, será necessário muito espaço extra. Portanto, o HotSpot apenas registra essas informações
em "locais específicos".Esses locais são chamados de Safepoint . A configuração do ponto seguro também exige que o fluxo de instrução de código seja executado até o ponto seguro antes de poder ser pausado para coleta de lixo.

Conjunto de memória e lista de cartões

Ao falar sobre teoria geracional, foi mencionado que, para resolver os problemas causados ​​​​por referências entre gerações de objetos, a estrutura de dados do conjunto de memória foi introduzida na nova geração para evitar adicionar toda a geração antiga ao intervalo de varredura do GC Roots. Na verdade, além do problema de referência de geração cruzada entre a nova geração e a antiga geração, todos os coletores de lixo envolvidos no comportamento de coleta de área parcial (GC parcial) enfrentarão o mesmo problema de referência de geração cruzada.

O conjunto de memória é uma estrutura de dados abstrata usada para registrar um conjunto de ponteiros da área de não coleta para a área de coleta . A implementação mais simples é registrar todos os objetos com referências de geração cruzada nesta estrutura de dados.

Mas para cenários de coleta de lixo, o coletor só precisa saber se existe um ponteiro para a área de coleta em uma determinada área de não coleta e não precisa saber as informações específicas desses ponteiros de geração cruzada, portanto, todos os registros contêm dados cruzados. -geração de referências. Os objetos serão muito redundantes e um desperdício de espaço. Você pode escolher uma granularidade de registro mais grosseira:

Precisão do comprimento da palavra : cada registro tem precisão de um comprimento de palavra de máquina
, indicando que a palavra contém um objeto ponteiro de geração cruzada. ponteiro de geração. Precisão : cada registro é preciso para uma área de memória , indicando que há objetos na área contendo ponteiros de geração cruzada

Entre eles, a solução de “precisão do cartão” refere-se ao uso de um método chamado tabela de cartões para implementar o conjunto de memória, que também é a forma mais comumente usada de implementação do conjunto de memória atualmente . A lógica padrão de marcação da tabela de cartões do HotSpot usa uma matriz de bytes:

CARD_TABLE[this.address >> 9] = 0;

Cada elemento da matriz de bytes CARD_TABLE corresponde a um bloco de memória de tamanho específico na área de memória que representa, denominado página de cartão . De modo geral, o tamanho da página do cartão é 2 elevado à enésima potência dos bytes. Pelo código acima, pode-se observar que a página do cartão utilizada pelo HotSpot é 2 elevado à 9ª potência, ou seja, 512 bytes (desloque o endereço para a direita por 9 bits para obter seus elementos correspondentes na matriz da tabela de cartões, então os endereços de 0 ~ 511 B são todos 0 após deslocar 9 bits para a direita, indicando que esses endereços 0 ~ 511 B correspondem à mesma página do cartão nº 0, então o tamanho da página do cartão é 512 B)

Enquanto houver um ou mais campos de objeto na página do cartão que possuam ponteiros intergeracionais, o valor do elemento correspondente da matriz da tabela de cartões será 1, o que significa que esse elemento está sujo; caso contrário, será marcado como 0

Durante a coleta de lixo, desde que os elementos sujos na tabela de cartões sejam filtrados, você pode descobrir facilmente quais blocos de memória da página do cartão contêm ponteiros de geração cruzada (ou seja, esses ponteiros vêm de outras áreas que não estão atualmente passando por coleta de lixo , mas eles apontam para objetos na área de coleta de lixo atual, então adicione esses ponteiros ao GC Roots) e, em seguida, adicione-os ao GC Roots para digitalizar juntos

barreira de gravação

Então, como manter os elementos da mesa de cartas? O momento em que os elementos da mesa de jogo ficam sujos é quando os objetos de outras áreas geracionais se referem a objetos desta área. No HotSpot, o status da lista de cartões é mantido através da tecnologia Write Barrier .

A barreira de gravação pode ser vista como o aspecto AOP da ação de "atribuição de campo de tipo de referência" no nível da máquina virtual . Quando um valor é atribuído a um objeto de referência, uma notificação circular (Around) é gerada para que o programa execute ações adicionais.A barreira de gravação antes da atribuição é chamada de barreira de pré-gravação, e a parte após a atribuição é chamada de pós-atribuição. barreira de gravação. Depois de aplicar a barreira de gravação, a
máquina virtual gera instruções correspondentes para todas as operações de atribuição. Independentemente de a referência da geração antiga para a nova ser atualizada, sobrecarga adicional será incorrida sempre que a referência for atualizada, mas de Claro, essa sobrecarga é a mesma que quando o Minor GC verifica toda a geração antiga. O custo ainda é muito menor em comparação com

Além disso, a tabela de cartões também enfrenta o problema de compartilhamento falso (falso compartilhamento) em cenários de alta simultaneidade , porque vários elementos da tabela de cartões compartilharão uma linha de cache.Se os objetos atualizados por diferentes threads estiverem nos locais correspondentes desses cartões elementos da tabela, Na área de memória, fará com que a mesma linha de cache seja escrita ao atualizar a tabela de cartões, afetando o desempenho.
Uma solução simples não é usar uma barreira de gravação incondicional, mas primeiro verificar a marca da mesa de cartas e sujar o elemento da mesa de cartas apenas se não tiver sido marcado:

if(CARD_TABLE[this.address >> 9] != 0)   CARD_TABLE[this.address] = 0;

Análise de acessibilidade simultânea

O algoritmo de análise de acessibilidade requer teoricamente que todo o processo seja baseado num instantâneo que possa garantir a consistência antes que a análise possa ser realizada.Isto significa que a execução do thread do utilizador deve ser congelada durante todo o processo para enumerar o nó raiz e atravessar o gráfico do objecto. para marcação. Então, por que a travessia do gráfico de objetos precisa ser feita em um instantâneo que garanta consistência?

Se o thread do usuário e o coletor funcionarem simultaneamente, pode haver duas consequências: uma é marcar erroneamente o objeto morto como alive , o que é tolerável, mas existem alguns objetos que escapam dessa coleçãolixo flutuanteIsso é tudo, a outra é marcar o objeto sobrevivente original como morto por engano . Esse tipo de erro é muito sério e causará erros no programa

Com a ajuda do exemplo da marcação tricolor , pode-se tirar a seguinte conclusão: O problema do "desaparecimento do objeto" ocorrerá quando e somente quando as duas condições a seguir forem atendidas ao mesmo tempo , ou seja, um objeto que deveria ser preto é erroneamente marcado como branco: o cedente (pode ser entendido como o thread do usuário) uma ou mais novas referências do objeto preto para o objeto branco são inseridas ; o cedente exclui todas as referências diretas ou indiretas do objeto cinza para o objeto branco (porque as relações de referência do objeto preto foram verificadas, não serão verificadas novamente, portanto, se um objeto branco for referenciado por ele, e o objeto branco não for referenciado por nenhum objeto cinza, a máquina virtual não encontrará esta referência relacionamento, então irá deletar o objeto branco. Na verdade, deve-se dizer que é um objeto preto, pois já está referenciado pelo objeto preto)

Então você só precisa destruir uma das duas condições:

  1. Atualização incremental(Atualização Incremental) destrói a primeira condição:
    quando o objeto preto insere um novo relacionamento de referência apontando para o objeto branco, o relacionamento de referência recém-inserido é registrado e, após a conclusão da varredura simultânea, esses relacionamentos de referência registrados são O objeto preto em é a raiz, verifique novamente .
    Em outras palavras, uma vez que o objeto preto tenha uma referência recém-inserida ao objeto branco, ele volta a ser um objeto cinza.
  2. instantâneo original(Snapshot At The Beginning, SATB) O que destrói é a segunda condição:
    quando o objeto cinza deseja deletar a referência ao objeto branco, a referência a ser deletada é gravada, e após a conclusão da varredura simultânea, essas referências gravadas são O O objeto cinza no relacionamento é a raiz e é verificado novamente . Em outras palavras, independentemente de o relacionamento de referência ser excluído ou não, a pesquisa será baseada no instantâneo do gráfico do objeto no momento em que a verificação acabou de ser iniciada. Isso é equivalente à referência do objeto cinza ao objeto branco que não está sendo excluído.

Através desses dois métodos, a varredura simultânea pode ser alcançada, mas a segunda varredura ainda precisa interromper o processo do usuário. No entanto, o tempo necessário para interromper o processo do usuário durante a segunda verificação ainda é pequeno comparado ao tempo necessário para interromper o processo do usuário em uma única verificação.
A inserção ou exclusão de registros de relacionamento de referência acima é feita através de barreiras de escrita.
O CMS implementado é baseado em atualizações incrementais para marcação simultânea; G1 é implementado usando snapshots originais.

coletor de lixo clássico

Coletor de lixo clássico

Serial

Serial não usa apenas um processador ou um encadeamento de coleta para completar a coleta de lixo, mas também quando coleta, todos os outros encadeamentos de trabalho devem ser suspensos até que a coleta seja concluída. Serial usa o algoritmo de cópia para Minor GC
; Serial Old usa marca - organizador Algoritmo para GC principal

ParNovo

O coletor ParNew é essencialmente uma versão simultânea multithread do coletor Serial . Ele também usa o algoritmo de cópia para Minor GC.

Limpeza paralela

Parallel Scavenge coleta a nova geração com base no algoritmo mark-copy . Sua característica é que seu foco é diferente de outros coletores. O foco de coletores como o CMS é minimizar o tempo de pausa dos threads do usuário durante a coleta de lixo, enquanto o objetivo do Parallel Scavenge é atingir um throughput controlável (Throughput), o assim -chamado rendimento é a proporção entre o tempo que o processador gasta executando o código do usuário e o tempo total consumido pelo processador:

Taxa de transferência = tempo para executar o código do usuário / (tempo para executar o código do usuário + tempo para executar a coleta de lixo) Taxa de transferência = tempo para executar o código do usuário \ /\ (tempo para executar o código do usuário + tempo para executar a coleta de lixo)Taxa de transferência=Hora de executar o código do usuário / ( Hora de executar o código do usuário  +hora de executar a coleta de lixo )

Quanto menor o tempo de pausa, mais adequado ele é para programas que precisam interagir com os usuários ou precisam garantir a qualidade da resposta do serviço. Uma boa velocidade de resposta pode melhorar a experiência do usuário; enquanto o alto rendimento pode fazer o uso mais eficiente do tempo do processador e completar as tarefas de computação do programa o mais rápido possível.Adequado principalmente para tarefas analíticas que operam em segundo plano sem exigir muita interação

Paralelo AntigoÉ uma versão de geração antiga do coletor Parallel Scavenge , que suporta coleta simultânea multithread e é implementada com base no algoritmo mark-sort . Assim como o Parallel Scavenge, concentre-se no rendimento

CMS

CMS ( Concorrente Mark Sweep Simultâneo\ Mark\ SweepVarredura de Marca Concorrente   ) O coletor é um coletor que visaobter o menor tempo de pausa para reciclagem , o que está em linha com a velocidade de resposta do serviço em questão . Espera - se que o sistema o evento de pausa será o mais curto possível para atender às necessidades do aplicativo para uma boa experiência de interação do usuário

O coletor CMS é baseado no algoritmo mark-sweep e seu processo de operação é dividido em: marca inicial ( marca inicial CMS CMS\ inicial\ marcaMarca inicial do CMS   ),marca simultânea ( marca simultânea do CMS CMS \ concurrent \ markCMS co n c u r n t m a r k   ),observação(observação CMS CMS\ observaçãoCMS re ma r k  ),varredura simultânea(varredura simultânea CMS CMS\ simultânea\ varreduraVarredura simultânea do CMS   ) _ _ _ _ _ _ _ _

  • A marcação inicial marca apenas objetos aos quais o GC Roots pode se associar diretamente, o que é muito rápido. Precisa interromper o tópico do usuário ("Stop The World")
  • A marcação simultânea é o processo de percorrer todo o gráfico do objeto a partir dos objetos diretamente associados do GC Roots. Este processo leva muito tempo, mas não precisa pausar os threads do usuário e pode ser executado simultaneamente com os threads de coleta de lixo (porque o método de atualização incremental é usado para resolver a simultaneidade quando o problema do "objeto morre")
  • A remarcação serve para corrigir os registros de marcação daquela parte do objeto que foi alterada devido à operação contínua do thread do usuário durante a marcação simultânea. Ela é operada por meio de atualizações incrementais e o thread do usuário também precisa ser pausado.
  • A limpeza simultânea da fase de limpeza exclui objetos mortos julgados na fase de marcação. Pode ser simultânea com o thread do usuário porque não há necessidade de mover objetos vivos e os objetos mortos não serão referenciados novamente (algoritmo de marcação clara)

CMS é a primeira tentativa bem-sucedida de máquinas virtuais HotSpot de buscar pausas baixas, mas tem pelo menos as três deficiências óbvias a seguir:

  • É muito sensível aos recursos do processador, pois busca baixas pausas, por isso é projetado para simultaneidade, portanto, no estágio de simultaneidade, embora atinja o propósito de não causar pausa nos threads do usuário, ele ocupará uma parte do thread, que isto é, ocupa parte do poder de computação do processador, o que torna o aplicativo mais lento e afeta o rendimento geral

  • Como o CMS não pode lidar com " lixo flutuante ", uma falha de "Falha no modo simultâneo" pode ocorrer, o que pode levar a outro GC completo "Stop The World". "Lixo flutuante" significa que durante as fases simultâneas de marcação e limpeza simultânea do CMS, os threads do usuário ainda estão em execução. Naturalmente, novos objetos de lixo continuarão a ser gerados enquanto o programa estiver em execução, mas esta parte dos objetos de lixo aparecerá. Depois o processo de marcação é concluído, o CMS não pode processá-los na coleção atual e tem que aguardar a próxima coleta de lixo para limpá-los. Essa parte do lixo é chamada de "lixo flutuante"

    Isso também ocorre porque o thread do usuário precisa continuar em execução durante a fase de coleta de lixo, portanto, espaço de memória suficiente precisa ser reservado para o thread do usuário usar . Portanto, o coletor CMS não pode esperar até que a geração antiga esteja quase cheia como outros coletores antes Para coletar, uma parte do espaço deve ser reservada para operação do programa durante a coleta simultânea. Ou seja, a coleta de lixo será realizada quando o espaço usado na geração antiga atingir um determinado limite, em vez de esperar até que todo o espaço seja usado.

  • O CMS é baseado em um algoritmo de marcação e varredura, portanto, uma grande quantidade de fragmentação de espaço será gerada no final da coleção.

Lixo primeiro (G1)

modelo de tempo morto

G1 foi o pioneiro na ideia de design de colecionador para coleta local e no formulário de layout de memória baseado em região. O layout de memória heap baseado em região é a chave para G1 atingir o objetivo de estabelecer um " modelo de tempo de pausa ". "Modelo de tempo de pausa" significa que ele pode suportar uma grande quantidade de tempo gasto na coleta de lixo dentro de um segmento de tempo de comprimento M milissegundos. A probabilidade não excede a meta de N milissegundos

GC Misto

Para todos os coletores antes do aparecimento do coletor G1, o escopo da coleta de lixo era toda a nova geração, toda a geração antiga ou todo o heap Java, enquanto G1 pode formar uma coleção (Conjunto de Coleções) para qualquer parte do heap memória ., CSet) para reciclagem, o critério de medição não é mais a qual geração ele pertence , mas qual pedaço de memória armazena a maior quantidade de lixo e tem os maiores benefícios de reciclagem . Este é o modo Mixed GC do coletor G1.

Em vez de insistir em um tamanho fixo e um número fixo de divisões de área geracional, o heap Java contínuo é dividido em múltiplas áreas independentes de igual tamanho, ou seja, regiões.Cada região pode desempenhar o papel de espaço Eden e espaço sobrevivente da nova geração como necessário. , ou o espaço da velha geração, o colecionador pode usar estratégias diferentes para lidar com regiões que desempenham papéis diferentes

Há também uma área especial Humongous na Região, que é especialmente usada para armazenar objetos grandes. O G1 acredita que desde que o tamanho ultrapasse a metade da capacidade de uma Região, ela pode ser considerada um objeto grande; e para aqueles super objetos grandes que excedem a capacidade de toda a região, serão armazenados em N regiões gigantescas consecutivas

Método de reciclagem

Embora o G1 ainda mantenha os conceitos de nova geração e de velha geração, a nova geração e a velha geração não são mais fixas, são uma série de coleções dinâmicas de áreas que não precisam ser contínuas.

A razão pela qual o coletor G1 pode estabelecer um modelo de tempo de pausa previsível é permitir que o coletor G1 rastreie o "valor" do acúmulo de lixo em cada região. O valor é a quantidade de espaço obtido pela reciclagem e o tempo necessário para a reciclagem . e, em seguida, manter uma lista de prioridades em segundo plano. Cada vez, de acordo com o tempo de pausa de coleta permitido definido pelo usuário (especificado através dos parâmetros -XX:MaxGCPauseMills, o padrão é 200 ms), as regiões com maiores ganhos de valor de reciclagem serão processadas primeiro. Este também é o nome "Lixo Primeiro" "A origem do

Este método de usar Região para dividir o espaço de memória e recuperar áreas priorizadas garante que o coletor G1 possa obter a maior eficiência de coleta possível dentro de um tempo limitado

Lidando com alguns detalhes

  1. A aplicação de conjuntos de memória no coletor G1 é mais complexa, cada uma de suas Regiões mantém seu próprio conjunto de memória. Esses conjuntos de memória registram os ponteiros apontados por outras Regiões e marcam o intervalo de páginas do cartão em que esses ponteiros se enquadram. É essencialmente uma tabela hash, onde Key é o endereço inicial de outras regiões, Value é um conjunto e os elementos armazenados nela são os números de índice da tabela de cartas.

  2. O coletor G1 usa o algoritmo de instantâneo original para resolver o problema de possíveis danos à estrutura do gráfico do objeto por threads do usuário durante a fase de marcação simultânea; além disso, a coleta de lixo é afetada pelos threads do usuário e também se reflete na alocação de memória dos threads recém-criados. objetos durante o processo de reciclagem. O programa deve continuar em execução. Novos objetos certamente continuarão a ser criados. G1 desenha dois ponteiros chamados TAMS (Top at Mark Start) para cada Região, dividindo uma parte do espaço da Região para novo objeto alocação durante o processo de reciclagem simultânea Simultaneidade Os endereços dos objetos recém-alocados durante a reciclagem devem estar acima dessas duas posições de ponteiro.

    O coletor G1 tem por padrão que os objetos acima deste endereço sejam marcados implicitamente, ou seja, estão ativos por padrão e não estão incluídos no escopo de reciclagem. Semelhante à "falha no modo simultâneo" no CMS, a falha causará GC completo. Se a velocidade de reciclagem de memória não conseguir acompanhar a velocidade de alocação de memória, G1 também será forçado a congelar a execução dos threads do usuário e causar GC completo.

processo de operação

O processo de operação pode ser dividido aproximadamente nas quatro etapas a seguir:

  1. Marcação inicial : marque apenas os objetos aos quais o GC Roots pode se associar diretamente e modifique o valor do ponteiro TAMS para que novos objetos possam ser alocados corretamente na região disponível durante a próxima fase de simultaneidade do thread do usuário.
  2. Marcação Simultânea: Realize análise de acessibilidade começando pelas Raízes do GC para descobrir os objetos a serem reciclados; após a conclusão da varredura do gráfico do objeto, os objetos com alterações de referência registradas pelo SATB durante a simultaneidade devem ser reprocessados
  3. Pontuação Final : Registro SATB obtido ao final da fase simultânea de processamento
  4. Triagem e reciclagem (contagem e evacuação de dados ao vivo): atualize as estatísticas da região, classifique o valor e o custo de reciclagem de cada região e formule um plano de reciclagem com base no tempo de pausa esperado pelo usuário. Você pode selecionar livremente qualquer número de Regiões para formar uma coleção de reciclagem. Em seguida, copie os objetos sobreviventes da parte da Região que decidiu ser reciclada para a Região vazia e, em seguida, limpe todo o espaço de toda a Região antiga.
    Como envolve o movimento de objetos ativos , o thread do usuário deve ser pausado e concluído em paralelo por vários threads coletores.

Sobre a configuração do tempo de pausa

A capacidade de especificar o tempo de pausa desejado pelo usuário é um recurso muito poderoso do G1, mas o “valor esperado” definido deve ser realista.

Embora esperemos que o tempo de pausa seja o mais curto possível, se o tempo de pausa for ajustado para ser muito baixo, o resultado provável é que, como o tempo de pausa alvo é muito curto, as coleções coletadas que podem ser selecionadas a cada vez ocupam apenas um pequena quantidade de memória heap. Uma parte, ou seja, o espaço de memória heap para cada limpeza é menor, a eficiência de cada limpeza não é alta e a velocidade de coleta do coletor não consegue acompanhar a velocidade do alocador, então o o lixo se acumulará lentamente e, eventualmente, preencherá o heap e fará com que um GC completo prejudique o desempenho, portanto, geralmente é mais razoável definir o tempo de pausa esperado para um ou duzentos milissegundos ou duzentos ou trezentos milissegundos

Marcos no desenvolvimento da tecnologia de coletores

A partir do G1, a orientação de design dos coletores de lixo mais avançados passou a ser a busca pela taxa de alocação de memória que possa lidar com o aplicativo , em vez da busca pela limpeza de todo o heap Java de uma só vez. também está coletando enquanto o aplicativo está alocando. Contanto que a velocidade de coleta possa acompanhar a velocidade de alocação de objetos , tudo funcionará perfeitamente.
Do ponto de vista da implementação de engenharia, esta nova ideia de design de coletor começou em G1, então G1 é o desenvolvimento da tecnologia de coletores. um marco

Entendimento pessoal: Pode-se dizer que essas duas orientações de design são uma espécie de dinâmica e uma espécie de estática: a primeira pode ajustar dinamicamente a quantidade de tarefas coletadas a cada vez de acordo com a configuração do tempo de pausa e outras informações; enquanto a última não não considere nenhuma informação, tente limpar a pilha todas as vezes. A abordagem dinâmica torna o coletor mais flexível e “mais inteligente”

Comparação G1 vs. CMS

  1. Tanto o G1 quanto o CMS dão muita atenção ao controle do tempo de pausa.

  2. Comparado com o CMS, o G1 tem muitas vantagens. Além de designs inovadores, como a capacidade de especificar o tempo máximo de pausa, o layout de memória das regiões e a determinação dinâmica de cobranças com base na receita, do ponto de vista teórico do algoritmo tradicional, o CMS usa um "mark-clear" algoritmo, enquanto G1 como um todo É implementado mark-organize ", mas de uma perspectiva local, ou seja, entre duas regiões, é implementado com base no algoritmo " mark-copy ". Ambos os algoritmos significam que nenhuma memória será gerada durante a operação do G1. A fragmentação, após a conclusão da coleta de lixo, pode fornecer memória disponível regular, e esse recurso é propício para a execução do programa a longo prazo. Quando o programa aloca memória para objetos grandes, ele não é fácil ser forçado a acionar a próxima coleção antecipadamente devido à incapacidade de encontrar espaço de memória contínuo.

  3. Claro, G1 também tem alguns pontos fracos em comparação: por exemplo, quando o programa do usuário está em execução, o uso de memória do G1 para coleta de lixo e a carga adicional de execução quando o programa está em execução são maiores do que o CMS.

    • Em termos de uso de memória : ambos usam tabelas de cartões para lidar com ponteiros de geração cruzada, mas a implementação da tabela de cartões do G1 é mais complexa e cada região deve ter uma tabela de cartões independente de sua função , o que resulta no conjunto de memória do G1. de espaço de memória; em comparação, a tabela de cartões CMS é muito simples, com apenas uma cópia, e só precisa tratar referências da geração antiga para a nova geração, e não vice-versa, porque os objetos da nova geração "vivem e morrem ". Instabilidade, as referências mudam com frequência, por isso é muito econômico economizar a sobrecarga de manutenção de referências da nova geração para outras áreas. Claro,
      o preço é quando ocorre CMS Old GC (entre todos os coletores, apenas CMS tem Old GC para a geração antiga). ), toda a nova geração deve ser escaneada como GC Roots, pois o CMS não mantém uma tabela de cartas apontando da nova geração para a geração antiga, e a tabela de cartas do G1 é mantida com base na Região , independentemente de a Região atualmente desempenhar o papel da geração antiga ou Os personagens da nova geração têm listas de cartas apontando para a geração da coleção da geração sem coleção.

    • Do ponto de vista da carga de execução , por exemplo, ambos usam barreiras pós-gravação para manter a tabela de cartas e, para implementar o algoritmo de busca de instantâneo original, G1 também precisa usar barreiras pré-gravação para rastrear alterações de ponteiro durante a simultaneidade (porque o instantâneo original precisa estar em Grave a referência antes de ser digitalizado, caso contrário, se a referência for realmente excluída após a digitalização, ela não será encontrada; somente se a referência for realmente excluída após a digitalização, a referência poderá ser gravada), o que é melhor que a barreira de gravação do CMS A implementação é mais complexa e demorada

Acho que você gosta

Origin blog.csdn.net/Pacifica_/article/details/123114023
Recomendado
Clasificación