Executamos vários serviços Java (Corretto JDK21) no AWS Elastic Container Service (ECS) Fargate. Cada serviço possui seu próprio container e queremos utilizar todos os recursos possíveis que estamos pagando para cada processo. Mas as etapas podem ser aplicadas ao EC2 e outras nuvens.
Os serviços estão executando trabalhos em lote e a latência não é importante, usamos Parallel GC (-XX: UseParallelGC). Talvez o G1 fosse melhor até com nossas tarefas, mas é assunto para pesquisa e postagem à parte.
Para usar toda a memória disponível, MaxHeapSize é um pouco menor que o tamanho da memória do contêiner. Mas depois de algum tempo notamos dois problemas, às vezes nossos contêineres eram eliminados porque usavam muita memória e às vezes recebíamos exceções OutOfMemoryError. Para corrigir o primeiro, aumentamos a lacuna entre o tamanho da memória do contêiner e MaxHeapSize e, para o segundo, aumentamos a memória dos contêineres como uma solução rápida e começamos a observar os despejos de heap.
Os heap dumps mostraram detalhes interessantes, o tamanho real do heap era menor que MaxHeapSize e o heap da geração jovem era minúsculo em comparação com a geração antiga.
Pesquisar na internet não ajudou a encontrar um bom guia sobre como ajustar os parâmetros JVM para o nosso caso, só encontrei alguns detalhes de alto nível sobre heaps e descrições de parâmetros. Decidi escrever este post para descrever as etapas que executei.
Os primeiros passos foram:
A taxa padrão para gerações jovens:velhas é de 1:2, e apenas parte da geração mais jovem é usada ao mesmo tempo para realizar GC. E após o início, a JVM alocou toda a memória conforme esperado, mas depois de algum tempo começou a diminuir o tamanho do heap da geração jovem para quase vários megabytes. Então, depois de algum tempo, usamos apenas ⅔ da memória disponível.
Depois de algumas pesquisas, encontrei um parâmetro para desabilitar a Política Adaptativa (-XX:-UseAdaptiveSizePolicy) e isso ajudou, o heap parou de diminuir e os intervalos entre as coletas de lixo aumentaram em uma ordem de magnitude ou até mais. O tempo consumido pelo GC também cresceu, mas não tanto.
A próxima etapa foi encontrar a lacuna ideal entre o tamanho da memória do contêiner. Por padrão, mesmo que InitialRAMPercentage=100, o JDK apenas aloca memória e não a utiliza, portanto não é mapeada. O Linux permite alocar mais memória virtual do que memória física. E o contêiner falha mais tarde, quando a memória é realmente mapeada (o JDK grava nele). -XX: AlwaysPreTouch altera esse comportamento. Infelizmente, parte da memória ainda não está mapeada, mas o encerramento do OOM acontece muito mais rápido. Depois de várias tentativas terminei com a próxima fórmula Container Memory Size - 1024MB para containers com 8GB de memória ou mais. Por exemplo, para um tamanho de memória de contêiner 8192, usamos -XX:MaxHeapSize=7168m.
Para otimizações adicionais, estamos pensando em alterar -XX:NewRatio para diminuir o tamanho da geração jovem e reduzir o tempo de GC. Mas isso depende do tempo de vida do objeto na aplicação.
Como mencionei antes, não encontrei nenhum bom guia com explicação detalhada dos parâmetros (o melhor que encontrei é vm-options-explorer) e etapas de ajuste. Seria ótimo se você pudesse compartilhar seu conhecimento e resultados.
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3