Construa um andador de ferro fundido no Spring Boot e no AppCDS


Compartilhamento de dados de classe de aplicativo (AppCDS) - recurso JVM para acelerar a inicialização e economizar memória. Tendo aparecido em sua infância no HotSpot em JDK 1.5 (2004), permaneceu por muito tempo muito limitado e até parcialmente comercial. Somente com o OpenJDK 10 (2018) ele foi disponibilizado a meros mortais, ao mesmo tempo em que ampliava o escopo. E o Java 13, lançado recentemente, tentou tornar esse aplicativo mais simples.


A idéia do AppCDS é "compartilhar" uma vez carregadas as classes entre instâncias da mesma JVM no mesmo host. Parece que isso deve ser ótimo para microsserviços, especialmente os "broilers" no Spring Boot com milhares de classes de bibliotecas, porque agora essas classes não precisam ser carregadas (analisadas e verificadas) a cada início de cada instância da JVM e não serão duplicadas na memória. Isso significa que o lançamento deve se tornar mais rápido e o consumo de memória deve ser menor. Maravilhoso, não é?


Tudo é assim, tudo é assim. Mas se você, o odnokhabryanin, costumava acreditar não nos sinais da avenida, mas em números e exemplos específicos, então seja bem-vindo ao kat - vamos tentar descobrir como realmente é ...


Em vez de isenção de responsabilidade


Antes você não é um guia para usar o AppCDS, mas um resumo dos resultados de um pequeno estudo. Eu estava interessado em entender como essa função da JVM é aplicável em meu projeto de trabalho e tentei avaliá-la da perspectiva de um desenvolvedor corporativo, estabelecendo o resultado neste artigo. Isso não incluiu tópicos como o uso do AppCDS no caminho do módulo, a implementação do AppCDS em outras máquinas virtuais (não o HotSpot) e os meandros do uso de contêineres. Mas há uma parte teórica para explorar o tópico, bem como uma parte experimental escrita para que você possa repetir a experiência por conta própria. Nenhum dos resultados ainda foi aplicado na produção, mas quem sabe como será o amanhã ...


Teoria


Uma breve introdução ao AppCDS


O conhecimento deste tópico pode ter ocorrido a você em várias fontes, por exemplo:


  • em um artigo de Nikolai Parlog (incluindo Java 13 pães, mas sem Spring Boot)
  • em um relatório e artigo de Volker Simonis (sem Java 13, mas com detalhes)
  • em um relatório do autor dessas linhas (sem Java 13, mas com ênfase no Spring Boot)

Para não participar da recontagem, destacarei apenas alguns pontos importantes para este artigo.


Em primeiro lugar, o AppCDS é uma extensão do recurso CDS que aparece há muito tempo no HotSpot, cuja essência é a seguinte:



Para dar vida às duas idéias, faça o seguinte (em termos gerais):


  1. Obtenha uma lista de classes que você deseja compartilhar entre instâncias de aplicativos
  2. Mesclar essas classes em um arquivo adequado para mapeamento de memória
  3. Conecte o arquivo morto a cada instância do aplicativo na inicialização

Parece que o algoritmo tem apenas 3 etapas - pegue e faça. Mas aqui as notícias começam, todo tipo de coisa.


O ruim é que, na pior das hipóteses, cada um desses itens se transforma em pelo menos um lançamento da JVM com suas próprias opções específicas, o que significa que todo o algoritmo é um malabarismo sutil do mesmo tipo de opções e arquivos. Isso não parece muito promissor, não é?


Mas há boas notícias: o trabalho para melhorar esse algoritmo está em andamento e, a cada versão do Java, seu aplicativo se torna mais fácil. Então, por exemplo:


  • No OpenJDK 10 e 11, você pode pular a etapa 1 se desejar compartilhar apenas as principais classes JDK, pois elas já foram compiladas para nós e colocadas em $JAVA_HOME\lib\classlist (≈1200 unidades.).
  • No OpenJDK 12, você pode pular a etapa 2 porque, junto com a lista de classes, o arquivo de distribuição também inclui um arquivo pronto, que é usado imediatamente e não requer uma conexão explícita.
  • Caso você queira compartilhar todo o resto (e geralmente apenas deseja)
    O OpenJDK 13 fornece arquivos Dynamic CDS - arquivos que são coletados durante a operação do aplicativo e salvos quando ele é ocupado. Isso permite recolher os pontos 1 e 2 em um ponto não muito confuso (embora nem tudo seja tão simples, mas mais adiante).

Portanto, não importa qual seja o processo de preparação do AppCDS, as três etapas listadas acima estão sempre por trás, apenas em alguns casos elas são veladas.


Como você provavelmente notou, com o advento do AppCDS, muitas classes de aplicativos começam uma vida dupla: elas vivem simultaneamente em seus lugares anteriores (na maioria das vezes, arquivos JAR) e em um novo arquivo compartilhado. Ao mesmo tempo, o desenvolvedor continua a alterá-los / removê-los / suplementá-los no mesmo local, e a JVM os retira do novo quando estão trabalhando. Não é preciso ser um adivinho para ver o perigo de tal situação: se nada for feito, mais cedo ou mais tarde cópias das classes vão corroer, e teremos muitos encantos do típico "inferno JAR". É claro que a JVM não pode impedir alterações de classe, mas deve ser capaz de detectar uma discrepância no tempo. No entanto, fazer isso comparando classes aos pares, mesmo por somas de verificação, é uma idéia; pode negar o restante dos ganhos de produtividade. Provavelmente, é por isso que os engenheiros da JVM não selecionaram as classes individuais como objeto de comparação, mas o caminho de classe inteiro e declararam na documentação do AppCDS: “O caminho de classe ao criar um archive compartilhado deve ser o mesmo (ou pelo menos um prefixo) das ativações subsequentes do aplicativo”.


Observe que o caminho de classe usado no momento da criação do arquivo morto deve ser o mesmo (ou um prefixo) do caminho de classe usado no tempo de execução.

Mas essa não é uma afirmação inequívoca, porque, como você se lembra, um caminho de classe pode ser formado de diferentes maneiras, como:


  • lendo arquivos .class de diretórios de pacotes compilados,
    por exemplo, java com.example.Main
  • varrendo diretórios com arquivos JAR ao usar curinga,
    por exemplo, java -cp mydir/* com.example.Main
  • lista explícita de arquivos JAR e / ou ZIP,
    por exemplo, java -cp lib1.jar;lib2.jar com.example.Main

(isso não inclui o fato de que o caminho da classe também pode ser configurado de maneira diferente, por exemplo, pelas opções da JVM -cp/-classpath/--class-path , pela variável de ambiente CLASSPATH ou pelo atributo do arquivo JAR do Class-Path da Class-Path a ser ativado)


Desses métodos, apenas um é suportado no AppCDS - enumeração explícita de arquivos JAR. Aparentemente, os engenheiros da HotSpot JVM achavam que comparar caminhos de classe no arquivo AppCDS e no aplicativo iniciado seria rápido o suficiente e confiável apenas se fossem especificados da maneira mais clara possível - com uma lista exaustiva usual.


O CDS / AppCDS suporta classes de arquivamento apenas de arquivos JAR.

É importante observar aqui que esta afirmação não é recursiva, ou seja, não se aplica a arquivos JAR dentro de arquivos JAR (a menos que se trate de CDS dinâmico, veja abaixo). Isso significa que os bonecos JAR habituais emitidos pelo Spring Boot não funcionarão exatamente como no AppCDS comum; você precisará se sentar.


Outro problema no trabalho do CDS é que os arquivos compartilhados são projetados na memória com endereços fixos (geralmente começando em 0x800000000 ). Isso por si só não é ruim, mas como a ASLR (Address Space Layout Randomization) é ativada por padrão na maioria dos sistemas operacionais, o intervalo de memória necessário pode estar parcialmente ocupado. O que a JVM do HotSpot faz nesse caso é a opção especial -Xshare que suporta três valores:


  • -Xshare:on CDS / AppCDS -Xshare:on vigor; se o intervalo estiver ocupado, a JVM sairá com um erro. Este modo não é recomendado para uso em produção , pois isso pode causar falhas esporádicas ao iniciar aplicativos.
  • -Xshare:off - (você) alterna o CDS / AppCDS; desativa completamente o uso de dados compartilhados (incluindo arquivos incorporados)
  • -Xshare:auto - o comportamento padrão da JVM quando, em caso de impossibilidade de alocar o intervalo de memória necessário, se entrega silenciosamente e carrega as classes normalmente.

No momento da redação deste artigo, a Oracle estava trabalhando apenas para amenizar esses problemas, mas um número de release ainda não foi atribuído.


Essas opções são parcialmente úteis para nós mais tarde, mas, por enquanto, vejamos ...


Aplicativos AppCDS


Existem várias maneiras de usar o AppCDS. arruinar sua vida otimizar o trabalho dos microsserviços. Eles variam muito em complexidade e lucro potencial, por isso é importante decidir imediatamente qual deles será discutido mais tarde.


O mais simples é usar nem mesmo o AppCDS, mas apenas o CDS - é quando apenas as classes da plataforma entram no arquivo compartilhado (consulte "Uma Breve Introdução ao AppCDS"). Excluiremos essa opção imediatamente porque, quando aplicada a microsserviços no Spring Boot, ela gera muito pouco lucro. Isso pode ser visto pela proporção do número de classes compartilhadas em sua distribuição geral usando o exemplo de um microsserviço real (consulte o segmento verde):



Mais complexo, mas promissor, é o uso do AppCDS completo, ou seja, a inclusão de classes de biblioteca e aplicativo no mesmo arquivo. Essa é uma família inteira de opções derivada de combinações do número de aplicativos participantes e do número de instâncias. A seguir, são apresentadas avaliações subjetivas dos autores sobre os benefícios e dificuldades de várias aplicações do AppCDS.


Não.AplicaçõesInstânciasLucro da CPULucro RAMDificuldade
1UmUm+±Baixo
2UmAlguns++++Baixo
3AlgunsUm de cada vez++++Alta
4AlgunsAlguns++++++Alta

Preste atenção:


  • No aplicativo para um aplicativo em uma instância (Nº 1), o lucro da memória pode ser zero ou até negativo (especialmente ao medir no Windows )
  • Criar o arquivo compartilhado correto requer ações, cuja complexidade não depende de quantas cópias o aplicativo será iniciado (compare pares de opções nºs 1-2 e 3-4)
  • Ao mesmo tempo, a transição de uma instância para várias, obviamente, aumenta o lucro para ambos os indicadores, mas não afeta a complexidade da preparação.

Neste artigo, alcançaremos apenas a opção n ° 2 (até o n ° 1), uma vez que é simples o suficiente para um conhecimento próximo do AppCDS e, somente para isso, sem truques extras, podemos usar os recentemente lançados JEP-350 Dynamic CDS Archives, que quero sentir em ação.


Arquivos CDS dinâmicos


Os arquivos Dynamic CDS do JEP-350 , uma das principais inovações do Java 13, foram projetados para simplificar o uso do AppCDS. Para sentir a simplificação, você deve primeiro entender a complexidade. Deixe-me lembrá-lo de que o algoritmo clássico de aplicativo “limpo” do AppCDS consiste em 3 etapas: (1) obtenha uma lista de classes compartilhadas, (2) crie um arquivo a partir delas e (3) execute o aplicativo com o arquivo conectado. Destas etapas, apenas a terceira é realmente útil, o restante é apenas uma preparação para isso. E embora a obtenção de uma lista de classes (etapa 1) possa parecer muito simples (em alguns casos, é opcional), na verdade, ao trabalhar com aplicativos não triviais, acaba sendo a mais difícil, especialmente no que diz respeito ao Spring Boot. Portanto, o JEP-350 é necessário apenas para eliminar essa etapa, ou melhor, automatizá-la. A idéia é que a própria JVM elabore uma lista das classes de que o aplicativo precisa e, em seguida, forme a partir delas o chamado arquivo “dinâmico”. Concordo, parece bom. Mas o problema é que agora fica claro em que ponto parar de acumular classes e continuar a colocá-las no arquivo. Anteriormente, no AppCDS clássico, escolhemos esse momento por conta própria e podemos até alternar entre essas ações para alterar algo na lista de classes antes de transformá-lo em um arquivo. Agora, isso está acontecendo automaticamente e apenas em um momento, para o qual os engenheiros da JVM escolheram, talvez, a única opção de comprometimento - o desligamento regular da JVM. Isso significa que o arquivo morto não será criado até que o aplicativo pare. Esta solução tem algumas consequências importantes:


  • No caso de uma falha na JVM, o archive não será criado, por mais maravilhosa que seja a lista de classes acumuladas até então (não é possível extraí-la posteriormente usando meios regulares).
  • O arquivo morto será criado apenas a partir das classes que conseguiram carregar durante a sessão do aplicativo. Para aplicativos da Web, isso significa que a criação de um arquivo morto iniciando e parando não é correta, pois muitas classes importantes não serão inseridas no arquivo morto. É necessário executar pelo menos uma solicitação HTTP no aplicativo (e é melhor executá-lo adequadamente em todos os cenários) para que todas as classes que ele realmente usa sejam carregadas.

Uma diferença importante entre os arquivos estáticos e dinâmicos é que eles sempre constituem um "complemento" sobre os arquivos estáticos básicos, que podem ser arquivos embutidos no kit de distribuição Java ou criados separadamente, em uma maneira clássica de três etapas.


Sintaticamente, o uso de Dynamic CDS Archives se resume a dois lançamentos da JVM com duas opções:


  1. Execução de avaliação com a opção -XX:ArchiveClassesAtExit=archive.jsa , no final do qual um archive dinâmico será criado (você pode especificar qualquer caminho e nome)
  2. Lançamento útil com a opção -XX:SharedArchiveFile=archive.jsa , que usará o archive criado anteriormente

A segunda opção não é diferente de conectar um arquivo estático regular. Mas se de repente o arquivo estático básico não estiver no local padrão (dentro do JDK), essa opção também poderá incluir uma indicação do caminho para ele, por exemplo:


 -XX:SharedArchiveFile=base.jsa:dynamic.jsa 

(no Windows, o separador de caminho deve ser o caractere ";")


Agora você conhece o AppCDS o suficiente para poder vê-lo em ação.


Prática


Coelho experimental


Para que nossa aplicação do AppCDS na prática não se limite a um HelloWorld típico, tomaremos como base a aplicação real no Spring Boot. Meus colegas e eu geralmente temos que assistir logs de aplicativos em servidores de teste remotos e assistir ao vivo, exatamente como eles são gravados. Para usar isso, um agregador de logs completo (como o ELK) geralmente não é apropriado; baixar arquivos de log infinitamente - por um longo tempo, e olhar para a saída cinza do console é deprimente. Portanto, criei um aplicativo da web que pode gerar quaisquer logs em tempo real diretamente para o navegador, colorir linhas por nível de importância (ao mesmo tempo em que formata XML), agregar vários logs em um e outros truques. É chamado ANALOG (como um "analisador de log", embora isso não seja verdade) e está no GitHub . Clique na imagem para ampliar:



Tecnicamente, esse é um aplicativo no Spring Boot + Spring Integration, sob o capô do qual tail , docker e kubectl (para oferecer suporte a logs de arquivos, contêineres do Docker e recursos do Kubernetes, respectivamente). Ele vem na forma do arquivo JAR clássico "grosso" do Spring Boot. No tempo de execução, as classes de K10K estão suspensas na memória do aplicativo, das quais a grande maioria são classes Spring e JDK. Obviamente, essas classes mudam muito raramente, o que significa que podem ser colocadas em um arquivo compartilhado e reutilizadas em todas as instâncias do aplicativo, economizando memória e CPU.


Experiência única


Agora vamos aplicar o conhecimento existente do Dynamic AppCDS ao coelho experimental. Como tudo é conhecido em comparação, precisaremos de um ponto de referência - o estado do programa com o qual compararemos os resultados obtidos durante o experimento.


Observações introdutórias


  • Todos os comandos adicionais são para Linux. As diferenças para Windows e macOS não são fundamentais.
  • A compilação JIT pode afetar visivelmente os resultados e, em teoria, pela pureza do experimento, ele pode ser desativado (com a opção -Xint , como foi feito no artigo mencionado), mas por uma questão de máxima credibilidade, decidiu-se não fazer isso.
  • Os seguintes números sobre a hora de início foram obtidos em um servidor de teste rápido. Em máquinas de trabalho, números semelhantes, em regra, são mais modestos, mas como não estamos interessados ​​em valores absolutos, mas em incrementos percentuais, consideramos essa diferença insignificante.
  • Para não entrar prematuramente na complexidade de medir a memória compartilhada, por enquanto, omitiremos obter leituras precisas em bytes. Em vez disso, introduzimos o conceito de " potencial CDS " , expresso como uma porcentagem do número de classes compartilhadas no número total de classes carregadas. É claro que isso é uma quantidade abstrata, mas, por outro lado, afeta diretamente o consumo real de memória; além disso, sua definição não depende do sistema operacional e, para seu cálculo, apenas os logs são suficientes.

Ponto de referência


Deixe este ponto ser o estado de um aplicativo baixado recentemente, ou seja, sem o uso explícito de qualquer AppCDS'ov e outros. Para avaliá-lo, precisamos de:


  1. Instale o OpenJDK 13 (por exemplo, a distribuição doméstica do Liberica , mas não a versão lite).
    Ele também precisa ser incluído na variável de ambiente PATH ou em JAVA_HOME , por exemplo, assim:


     export JAVA_HOME=~/tools/jdk-13 

  2. Faça o download do ANALOG (no momento da redação, a versão mais recente era a v0.12.1).


    Se necessário, você pode especificar no arquivo config/application.yaml no parâmetro server.address o nome do host externo para acessar o aplicativo (por padrão, localhost é especificado lá).


  3. Ative o log de carregamento de classe da JVM.
    Para fazer isso, você pode JAVA_OPTS a variável de ambiente JAVA_OPTS com este valor:


     export JAVA_OPTS=-Xlog:class+load=info:file=log/class-load.log 

    Essa opção será passada para a JVM e instruirá a fonte de cada classe.


  4. Execute um teste:


    1. Execute o aplicativo com o script bin/analog
    2. Abra http: // localhost: 8083 no navegador, aperte botões e daws
    3. Pare o aplicativo pressionando Ctrl+C no console de scripts bin/analog

  5. Pegue o resultado (dos arquivos no diretório log/ )


    • Número total de classes carregadas (por class-load.log ):


       cat class-load.log | wc -l 10463 

    • Quantos deles são baixados de um arquivo compartilhado (de acordo com ele):


       grep -o 'source: shared' - class-load.log 1146 

    • Tempo médio de início (após várias séries; por analog.log ):


       grep -oE '\(JVM running for .+\)' analog.log | grep -oE '[0-9]\.[0-9]+' | awk '{ total += $1; count++ } END { print total/count }' 4.5225 



Portanto, nesta etapa, o potencial do CDS era 1146/10463=0,1095 ± 11% . Se você está surpreso de onde vieram as classes compartilhadas (afinal, ainda não incluímos nenhum AppCDS), lembro que a partir da 12ª versão, o JDK inclui o arquivo final do CDS $JAVA_HOME/lib/server/classes.jsa , construído por não menos do que lista pronta de classes:


 cat $JAVA_HOME/lib/classlist | wc -l 1170 

Agora, depois de avaliar o estado inicial do aplicativo, podemos aplicar o AppCDS a ele e, por comparação, entender o que isso oferece.


Experiência central


Como a documentação nos foi fornecida, para criar um archive dinâmico do AppCDS, você precisa executar apenas uma execução de avaliação do aplicativo com a opção -XX:ArchiveClassesAtExit . A partir do próximo lançamento, o arquivo pode ser usado e obter lucro. Para verificar isso no mesmo coelho experimental (AnaLog), você precisa:


  1. Adicione a opção especificada ao comando run:


     export JAVA_OPTS="$JAVA_OPTS -XX:ArchiveClassesAtExit=work/classes.jsa" 

  2. Estender o log:


     export JAVA_OPTS="$JAVA_OPTS -Xlog:cds=debug:file=log/cds.log" 

    Esta opção forçará o processo de construção de um arquivo CDS a ser registrado quando o aplicativo for parado.


  3. Execute o mesmo teste que o ponto de referência:


    1. Execute o aplicativo com o script bin/analog
    2. Abra http: // localhost: 8083 no navegador, aperte botões e daws
    3. Pare o aplicativo pressionando Ctrl+C no console de scripts bin/analog
      Depois disso, um tremendo calçado com todos os tipos de aviso deve cair no console e o log/cds.log deve ser preenchido com detalhes; eles ainda não nos interessam.

  4. Alterne o modo de inicialização de teste para útil:


     export JAVA_OPTS="-XX:SharedArchiveFile=work/classes.jsa -Xlog:class+load=info:file=log/class-load.log -Xlog:class+path=debug:file=log/class-path.log" 

    Aqui não suplementamos a variável JAVA_OPTS , mas a apagamos com novos valores que incluem (1) usando um arquivo compartilhado, (2) fontes de classe de log e (3) verificação de caminho de classe de log.


  5. Execute um lançamento útil do aplicativo de acordo com o esquema do parágrafo 3.


  6. Pegue o resultado (dos arquivos no diretório log/ )


    • Verificando se o AppCDS realmente foi aplicado (pelo class-path.log ):


       [0.011s][info][class,path] type=BOOT [0.011s][info][class,path] Expecting BOOT path=/home/upc/tools/jdk-13/lib/modules [0.011s][info][class,path] ok [0.011s][info][class,path] type=APP [0.011s][info][class,path] Expecting -Djava.class.path=/home/upc/tmp/analog/lib/analog.jar [0.011s][info][class,path] ok 

      As marcas ok após as linhas type=BOOT e type=APP indicam a abertura, verificação e carregamento bem-sucedidos dos arquivos CDS embutidos e aplicados, respectivamente.


    • Número total de classes carregadas (por class-load.log ):


       cat class-load.log | wc -l 10403 

    • Quantos deles são baixados de um arquivo compartilhado (de acordo com ele):


       grep -o 'source: shared' -c class-load.log 6910 

    • Tempo médio de início (após várias séries; por arquivo analog.log ):


       grep -oE '\(JVM running for .+\)' analog.log | grep -oE '[0-9]\.[0-9]+' | awk '{ total += $1; count++ } END { print total/count }' 4.04167 



Mas nesta etapa, o potencial do CDS já era 6910/10403≈0,66 = 66% , ou seja, aumentou 55% em comparação com o ponto de referência. Ao mesmo tempo, o tempo médio de inicialização foi reduzido em (4,5225-4,04167)=0,48 segundos, ou seja, O início é mais rápido em ± 10,6% do valor inicial.


Análise de Resultados


O título de trabalho do item é: "Por que tão pouco?"


Fizemos tudo de acordo com as instruções, mas nem todas as classes estavam no arquivo. O número deles afeta o tempo de lançamento não menos que o poder computacional da máquina do experimentador, portanto, nos concentraremos nesse número.


Se você se lembra, log/cds.log arquivo log/cds.log criado durante a parada do aplicativo experimental após a execução da avaliação. Nesse arquivo HotSpot, a JVM gentilmente notava classes de aviso para cada classe que não aparecia no arquivo CDS. Aqui está o número total de tais marcas:


 grep -o '[warning]' cds.log -c 3591 

Considerando que apenas 10K + classes são mencionadas no log de class-load.log e 66% delas são carregadas do archive, não é difícil entender que as 3600 classes listadas no cds.log são os 44% "ausentes" do potencial do CDS. Agora você precisa descobrir por que eles foram ignorados.


Se você olhar para o log cds.log, verifica-se que existem apenas 4 razões exclusivas para ignorar as classes. Aqui estão exemplos de cada um deles:


 Skipping org/springframework/web/client/HttpClientErrorException: Not linked Pre JDK 6 class not supported by CDS: 49.0 org/jrobin/core/RrdUpdater Skipping java/util/stream/Collectors$$Lambda$554: Unsafe anonymous class Skipping ch/qos/logback/classic/LoggerContext: interface org/slf4j/ILoggerFactory is excluded 

Entre todas as 3591 aulas perdidas, esses motivos são encontrados aqui com tanta frequência:



Dê uma olhada neles:


  • Unsafe anonymous class
    JVM “” , -, .


  • Not linked
    , “” , , . , StackOverflow . , , “” () JAR- , AppCDS. , ( ).


  • Pre JDK 6 class
    , CDS Java 5. class- , CDS . , , 6, Java, . - , runtime- (, slf4j).


  • Skipping ... : super class/interface ... is excluded
    , “” . CDS', . Por exemplo:


     [warning][cds] Pre JDK 6 class not supported by CDS: 49.0 org/slf4j/spi/MDCAdapter [warning][cds] Skipping ch/qos/logback/classic/util/LogbackMDCAdapter: interface org/slf4j/spi/MDCAdapter is excluded 


Conclusão


CDS 100%.

, , , , , . .



JEP-310 , AppCDS JDK. . , . CDS (, , ) .


Para clonar o coelho experimental (execute o AnaLog em várias instâncias), precisamos alterar algo nas configurações; isso permitirá que os processos levantados não "cotovelem". Graças ao Spring Boot, você pode fazer isso sem editar ou copiar nenhum arquivo; quaisquer configurações podem ser substituídas pelas opções da JVM. O encaminhamento dessas opções a partir de uma variável de ambiente ANALOG_OPTSfornece um script de inicialização, gentilmente gerado por Gradle.


 export ANALOG_OPTS="-Djavamelody.enabled=false -Dlogging.config=classpath:logging/logback-console.xml" export ANALOG_OPTS="$ANALOG_OPTS -Dnodes.this.agentPort=7801 -Dserver.port=8091" 

JavaMelody, , , . TCP- ; .


, , JVM AppCDS . JAVA_OPTS JVM Unified Logging Framework :


 export JAVA_OPTS="-Xlog:class+load=info:file=log/class-load-%p.log -Xlog:class+path=debug:file=log/class-path-%p.log" export JAVA_OPTS="$JAVA_OPTS -XX:SharedArchiveFile=work/classes.jsa" 

%p , JVM (PID). AppCDS , ( ).



, . . :


  1. server.port nodes.this.agentPort , :


     export ANALOG_OPTS="$ANALOG_OPTS -Dnodes.this.agentPort=7801 -Dserver.port=8091" 

    , ( ).


  2. bin/analog


    () http://localhost:8091 ,


  3. PID ( ), :


     pgrep -f analog 13792 

  4. pmap ( ):


     pmap -XX 13792 | sed -n -e '2p;$p' Address Perm Offset Device Inode Size KernelPageSize MMUPageSize Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous LazyFree AnonHugePages ShmemPmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked ProtectionKey VmFlagsMapping 3186952 1548 1548 328132 325183 3256 0 10848 314028 212620 314024 0 0 0 0 0 0 0 325183 0 KB 

    ; .


  5. 1-4 (, ).




pmap . CDS' . , , PSS:


The "proportional set size" (PSS) of a process is the count of pages it has in memory, where each page is divided by the number of processes sharing it. So if a process has 1000 pages all to itself, and 1000 shared with one other process, its PSS will be 1500.

, , “ ” . , .


PSS , :


Iteration:12345
PSS of inst#1:339 088313 778305 517301 153298 604
PSS of inst#2:314 904306 567302 555299 919
PSS of inst#3:314 914311 008308 691
PSS of inst#4:306 563304 495
PSS of inst#5:294 686
Average:339 088314 341308 999305 320301 279

, - :


  • “”
  • , PSS
  • “” , PSS

, . AppCDS. , -XX:SharedArchiveFile=work/classes.jsa -Xshare:off , CDS . , .



:


  • PSS AppCDS CDS.
    . , , HelloWorld- JVM CDS 2 , CDS. PSS CDS, . :


  • PSS AppCDS 2- ; 3- .
    , , , . , AppCDS, , , 3- .
    : , CDS? :


  • CDS/AppCDS JVM , PSS . , , pmap , “” sed '. :


     pmap -X `pgrep -f analog` 14981: # ... Address Perm Offset Device Inode Size Rss Pss ... Mapping # ... ... 7faf5e31a000 r-xp 00000000 08:03 269427 17944 14200 14200 ... libjvm.so # ... ... 7faf5f7f9000 r-xp 00000000 08:03 1447189 1948 1756 25 ... libc-2.27.so 

    ( Mapping ) , “” . JVM ( libjvm.so ), ( libc-2.27.so ). :


    For the Java VM, the read-only parts of the loaded shared libraries (ie libjvm.so ) can be shared between all the VM instances running at the same time. This explains why, taking together, the two VM's consume less memory (ie have a smaller memory footprint) than the simple sum of their single resident set sizes when running alone.


. , , . , , JVM , Java- . GeekOut:



, , , AppCDS , .. Java-. , JVM, , - .


VisualVM Metaspace AppCDS , :


AppCDS



AppCDS



, 128 Metaspace AppCDS 64.2 MiB / 8.96 MiB ≈7,2 , CDS . (. ) 66.4 MiB / 13.9 MiB ≈4,8 . , AppCDS , Metaspace. Metaspace, , CDS .


Em vez de uma conclusão


Spring Boot AppCDS – JVM, .


  • JEP-350 Dynamic CDS Archives – JDK 13.
  • Spring Boot ó CDS ( ). , 100% - 66% . , ≈11% ( 15%, ).
  • , 5- PSS ( ). , AppCDS , , 8% (PSS). , CDS, , . AppCDS .
  • Metaspace, , AppCDS 5 , CDS.

, , AppCDS, , “killer feature”. Spring Boot. , , AppCDS . , , AppCDS Spring Boot. , …


by Nick Fewings on Unsplash

Source: https://habr.com/ru/post/pt472638/


All Articles