Gerenciamento de memória Java

Observação

Azure Spring Apps é o novo nome do serviço Azure Spring Cloud. Embora o serviço tenha um novo nome, você verá o nome antigo em alguns locais por um tempo enquanto trabalhamos para atualizar ativos como capturas de tela, vídeos e diagramas.

Este artigo se aplica ao: ✔️ nível Básico/Standard ✔️ nível Enterprise

Este artigo descreve vários conceitos relacionados ao gerenciamento de memória Java para ajudá-lo a entender o comportamento dos aplicativos Java hospedados no Azure Spring Apps.

Modelo de memória Java

A memória de um aplicativo Java tem várias partes e há diferentes maneiras de dividir as partes. Este artigo discute a memória Java como dividida em memória de heap, memória não heap e memória direta.

Memória heap

A memória heap armazena todas as instâncias e matrizes de classe. Cada JVM (máquina virtual Java) tem apenas uma área de heap, que é compartilhada entre threads.

O Spring Boot Actuator pode observar o valor da memória de heap. O Spring Boot Actuator usa o valor de heap como parte de jvm.memory.used/committed/max. Para obter mais informações, consulte a seção jvm.memory.used/committed/max em Ferramentas para solucionar problemas de memória.

A memória do heap é dividida em geração jovem e geração antiga. Esses termos são descritos na lista a seguir, juntamente com os termos relacionados.

  • Geração jovem: todos os novos objetos são alocados e envelhecidos na geração jovem.

    • Espaço do Éden: novos objetos são alocados no espaço do Éden.
    • Espaço de sobrevivente: os objetos serão movidos do Éden para o espaço sobrevivente depois de sobreviver a um ciclo de coleta de lixo. O espaço de sobrevivente pode ser dividido em duas partes: s1 e s2.
  • Geração antiga: também chamada de espaço de posse. Objetos que permaneceram nos espaços sobreviventes por muito tempo serão movidos para a geração antiga.

Antes do Java 8, outra seção chamada geração permanente também fazia parte do heap. A partir do Java 8, a geração permanente foi substituída pelo metaspace na memória não heap.

Memória não heap

A memória não heap é dividida nas seguintes partes:

  • A parte da memória não heap que substituiu a geração permanente (ou permGen) começando pelo Java 8. O Spring Boot Actuator observa esta seção e a usa como parte de jvm.memory.used/committed/max. Aqui, jvm.memory.used/committed/max é a soma da memória heap e da antiga parte permGen da memória não heap. A geração permanente anterior é composta pelas seguintes partes:

    • Metaspace, que armazena as definições de classe carregadas por carregadores de classe.
    • Espaço de classe compactado, que é para ponteiros de classe compactados.
    • Cache de código, que armazena o código nativo compilado pelo JIT.
  • Outra memória, como a pilha de threads, que não é observada pelo Spring Boot Actuator.

Memória direta

A memória direta é a memória nativa alocada por java.nio.DirectByteBuffer, que é usada em bibliotecas de terceiros, como nio e gzip.

O Spring Boot Actuator não observa o valor da memória direta.

O diagrama a seguir resume o modelo de memória Java descrito na seção anterior.

Diagrama que mostra o modelo de memória Java.

Coleta de lixo de Java

Há três termos referentes à GC (Coleta de Lixo Java): "GC Secundária", "GC Principal" e "GC Completa". Esses termos não são definidos claramente na especificação JVM. Aqui, consideramos "GC Principal" e "GC Completa" equivalentes.

A GC Secundária é executada quando o espaço do Eden está cheio. Ela remove todos os objetos mortos na geração jovem e move objetos vivos do espaço do Eden para s1 do espaço sobrevivente, ou de s1 para s2.

O GC Completo ou o GC Principal faz a coleta de lixo em todo o heap. A GC Completa também pode coletar partes que só podem ser limpas por ela, como metaespaço e memória direta.

O tamanho máximo do heap influencia a frequência da GC secundária e da GC completa. O metaspace máximo e o tamanho máximo de memória direta influenciam a GC Completa.

Quando você define o tamanho máximo do heap como um valor menor, as coletas de lixo ocorrem com mais frequência, o que reduz um pouco o aplicativo, mas limita melhor o uso da memória. Quando você define o tamanho máximo do heap como um valor mais alto, as coletas de lixo ocorrem com menos frequência, o que pode criar mais risco de OOM (memória insuficiente). Para obter mais informações, consulte a seção Tipos de problemas de falta de memória de Problemas de reinicialização do aplicativo causados ​​por problemas de memória insuficiente.

O metaespaço e a memória direta só podem ser coletados pela GC Completa. Quando o metaespaço ou a memória direta estiver cheio, ocorrerá uma GC Completa.

Configurações de memória do Java

As seções a seguir descrevem aspectos importantes da configuração de memória do Java.

Conteinerização do Java

Os aplicativos no Azure Spring Apps são executados em ambientes de contêiner. Para obter mais informações, consulte Containerize seus aplicativos Java.

Opções importantes de JVM

Você pode configurar o tamanho máximo de cada parte da memória usando opções JVM. Você pode definir opções de JVM usando comandos da CLI do Azure ou por meio do portal do Azure. Para obter mais informações, consulte a seção Modificar configurações para corrigir problemas das Ferramentas para solucionar problemas de memória.

A lista a seguir descreve as opções de JVM:

  • Configuração do tamanho de heap

    • -Xms define o tamanho inicial do heap por valor absoluto.
    • -Xmx define o tamanho máximo do heap por valor absoluto.
    • -XX:InitialRAMPercentage define o tamanho inicial do heap pela porcentagem do tamanho do heap/tamanho da memória do aplicativo.
    • -XX:MaxRAMPercentage define o tamanho máximo do heap pela porcentagem do tamanho do heap/tamanho da memória do aplicativo.
  • Configuração de tamanho de memória direta

    • -XX:MaxDirectMemorySize define o tamanho máximo de memória direta por valor absoluto. Para saber mais, confira MaxDirectMemorySize na documentação da Oracle.
  • Configuração de tamanho do metaespaço

    • -XX:MaxMetaspaceSize define o tamanho máximo do metaspace por valor absoluto.

Tamanho máximo de memória padrão

As seções a seguir descrevem como os tamanhos máximos de memória padrão são definidos.

Tamanho máximo de heap padrão

O Azure Spring Apps define o tamanho máximo de memória de heap padrão como cerca de 50% a 80% da memória do aplicativo para aplicativos Java. Especificamente, o Azure Spring Apps usa as seguintes configurações:

  • Se a memória < do aplicativo for de 1 GB, o tamanho máximo de heap padrão será de 50% da memória do aplicativo.
  • Se 1 GB <= a memória do aplicativo < de 2 GB, o tamanho máximo de heap padrão será 60% da memória do aplicativo.
  • Se 2 GB <= a memória do aplicativo < de 3 GB, o tamanho máximo de heap padrão será 70% da memória do aplicativo.
  • Se 3 GB <= a memória do aplicativo, o tamanho máximo de heap padrão será 80% da memória do aplicativo.

Tamanho máximo de memória direta padrão

Quando o tamanho máximo de memória direta não é definido usando opções JVM, a JVM define automaticamente o tamanho máximo de memória direta para o valor retornado por Runtime.getRuntime.maxMemory(). Esse valor é aproximadamente igual ao tamanho máximo da memória do heap. Para obter mais informações, consulte o arquivo VM.java do JDK 8.

Layout de uso de memória

O tamanho do heap é influenciado pela sua taxa de transferência. Basicamente, ao configurar, você pode manter o tamanho máximo de heap padrão, o que deixa memória razoável para outras partes.

O tamanho do metaespaço depende da complexidade do código, como o número de classes.

O tamanho da memória direta depende da taxa de transferência e do uso de bibliotecas de terceiros, como nio e gzip.

A lista a seguir descreve um exemplo de layout de memória típico para aplicativos de 2 GB. Você pode consultar essa lista para definir as configurações de tamanho da memória.

  • Memória total (2048M)
  • Memória de heap: Xmx é 1433,6M (70% da memória total). O valor de referência do uso diário de memória é 1200M.
    • Geração jovem
      • Espaço Survivor (S0, S1)
      • Espaço Eden
    • Geração antiga
  • Memória não heap
    • Parte observada (observada pelo Spring Boot Actuator)
      • Metaspace: o valor de referência de uso diário é 50M-256M
      • Cache de código
      • Espaço de classe compactado
    • Parte não observada (não observada pelo Spring Boot Actuator): o valor de referência de uso diário é 150M-250M.
      • Pilha de threads
      • GC, símbolo interno e outros
  • Memória direta: o valor de referência de uso diário é 10M-200M.

O diagrama a seguir mostra as mesmas informações. Números em cinza são os valores de referência do uso diário de memória.

Diagrama do layout de memória típico para aplicativos de 2 GB.

No geral, ao configurar tamanhos máximos de memória, você deve considerar o uso de cada parte na memória e a soma de todos os tamanhos máximos não deve exceder a memória total disponível.

OOM do Java

OOM significa que o aplicativo está sem memória. Há dois conceitos diferentes: OOM de contêiner e OOM JVM. Para obter mais informações, confira Problemas de reinicialização do aplicativo causados por problemas de memória insuficiente.

Confira também