Gerenciamento de memória Java

Nota

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

Este artigo aplica-se a: ✔️ Basic/Standard ✔️ Enterprise

Este artigo descreve vários conceitos relacionados ao gerenciamento de memória Java para ajudá-lo a entender o comportamento de 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 dividida em memória heap, memória não-heap e memória direta.

Memória de pilha

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

Spring Boot Actuator pode observar o valor da memória de pilha. O Spring Boot Actuator usa o valor de pilha como parte do 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 de pilha é dividida em geração jovem e geração velha. Estes 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 Eden: novos objetos são alocados no espaço Eden.
    • Espaço do sobrevivente: os objetos serão movidos do Éden para o espaço do sobrevivente depois de sobreviver a um ciclo de coleta de lixo. O espaço do sobrevivente pode ser dividido em duas partes: s1 e s2.
  • Geração antiga: também chamada de espaço ocupado. Os objetos que permaneceram nos espaços sobreviventes por muito tempo serão transferidos 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 metaespaço na memória não-heap.

Memória sem 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 com Java 8. O Spring Boot Actuator observa esta secção e toma-a como parte do jvm.memory.used/committed/max. Em outras palavras, jvm.memory.used/committed/max é a soma da memória de pilha e a antiga parte permGen da memória não-heap. A antiga geração permanente é composta pelas seguintes partes:

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

Memória direta

A memória direta é a memória nativa alocada pela 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 Java

Há três termos referentes à Coleta de Lixo Java (GC): "GC Menor", "GC Maior" e "GC Completo". Esses termos não estão claramente definidos na especificação da JVM. Aqui, consideramos "GC Maior" e "GC Completo" equivalentes.

O GC menor é executado quando o espaço do Éden está cheio. Ele remove todos os objetos mortos na geração jovem e move objetos vivos do espaço Eden para s1 do espaço sobrevivente, ou de s1 para s2.

GC completo ou GC principal faz coleta de lixo em toda a pilha. O GC completo também pode coletar partes como metaespaço e memória direta, que podem ser limpas apenas pelo GC completo.

O tamanho máximo da pilha influencia a frequência de GC menor e GC completo. O metaespaço máximo e o tamanho máximo da memória direta influenciam o GC completo.

Quando você define o tamanho máximo da pilha para um valor mais baixo, as coletas de lixo ocorrem com mais frequência, o que atrasa um pouco o aplicativo, mas limita melhor o uso de memória. Quando você define o tamanho máximo de heap para um valor mais alto, as coletas de lixo ocorrem com menos frequência, o que pode criar mais risco de falta de memória (OOM). 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 falta de memória.

O metaespaço e a memória direta podem ser coletados apenas por GC completo. Quando o metaespaço ou a memória direta estiver cheio, ocorrerá um GC completo.

Configurações de memória Java

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

Contentorização Java

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

Opções importantes da JVM

Você pode configurar o tamanho máximo de cada parte da memória usando as opções da JVM. Você pode definir opções da 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 de Ferramentas para solucionar problemas de memória.

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

  • Configuração do tamanho da pilha

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

    • -XX:MaxDirectMemorySize Define o tamanho máximo de memória direta por valor absoluto. Para obter mais informações, consulte MaxDirectMemorySize na documentação do Oracle.
  • Configuração do tamanho do metaespaço

    • -XX:MaxMetaspaceSize Define o tamanho máximo do metaespaço 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 padrão de memória de pilha para 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 padrão do heap será 50% da memória do aplicativo.
  • Se 1 GB <= a memória < do aplicativo 2 GB, o tamanho máximo padrão da pilha será 60% da memória do aplicativo.
  • Se 2 GB <= a memória < do aplicativo 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 as opções da 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 de memória de pilha. Para obter mais informações, consulte o arquivo de VM.java JDK 8.

Layout de uso de memória

O tamanho da pilha é 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 seu código, como o número de classes.

O tamanho direto da memória depende da sua taxa de transferência e do seu 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. Pode consultar esta lista para definir as definições de tamanho da memória.

  • Memória total (2048M)
  • Memória de pilha: 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 de sobrevivência (S0, S1)
      • Espaço Éden
    • Geração antiga
  • Memória sem heap
    • Parte observada (observada pelo atuador de bota de mola)
      • Metaespaço: o valor de referência de uso diário é 50M-256M
      • Cache de código
      • Espaço de classe comprimido
    • Parte não observada (não observada pelo Spring Boot Actuator): o valor de referência de uso diário é 150M-250M.
      • Pilha de roscas
      • 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. Os números a cinzento são os valores de referência da utilização diária da memória.

Diagrama de 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 o total de memória disponível.

Java OOM

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

Consulte também