Olá pessoal! Queremos coincidir a tradução do material de hoje com o lançamento de um novo segmento no curso
Java Developer , que começa amanhã. Bem, vamos começar.
A JVM pode ser uma fera complexa. Felizmente, a maior parte dessa complexidade está oculta e, como desenvolvedores de aplicativos e responsáveis pela implantação, muitas vezes não precisamos nos preocupar muito com isso. Embora devido à crescente popularidade da tecnologia para implantar aplicativos em contêineres, vale a pena prestar atenção à alocação de memória na JVM.
Dois tipos de memóriaA JVM divide a memória em duas categorias principais: heap e não heap. Um heap é uma parte da memória da JVM com a qual os desenvolvedores estão mais familiarizados. Os objetos criados pelo aplicativo são armazenados aqui. Eles permanecem lá até serem removidos pelo coletor de lixo. Normalmente, o tamanho da pilha que o aplicativo usa varia de acordo com a carga atual.
A memória fora da pilha é dividida em várias áreas. No HotSpot, você pode usar o mecanismo de
rastreamento de memória nativa (NMT) para explorar as áreas dessa memória. Observe que, embora o NMT não rastreie o uso de toda a memória nativa (
por exemplo, ele não rastreia a alocação de memória nativa por código de terceiros ), seus recursos são suficientes para a maioria dos aplicativos típicos do Spring. Para usar o NMT, execute o aplicativo com a
-XX:NativeMemoryTracking=summary
e use
Resumo do jcmd VM.native_memory, consulte informações sobre a memória usada.
Vejamos como usar o NMT como exemplo do nosso velho amigo Petclinic . O diagrama abaixo mostra o uso da memória JVM de acordo com os dados NMT (menos sua própria sobrecarga NMT) ao iniciar o Petclinic com um tamanho de heap máximo de 48 MB (
-Xmx48M
):

Como você pode ver, a memória fora do heap é responsável pela maior parte da memória JVM usada, e a memória heap é apenas um sexto do total. Nesse caso, é de aproximadamente 44 MB (dos quais 33 MB foram usados imediatamente após a coleta de lixo). O uso de memória fora da pilha totalizou 223 MB.
Áreas de memória nativaEspaço de classe compactado : usado para armazenar informações sobre classes carregadas. Limitado ao parâmetro
MaxMetaspaceSize
. Uma função do número de classes que foram carregadas.
Nota do tradutorPor alguma razão, o autor escreve sobre o espaço de classe compactado e não sobre toda a área de classe. A área de espaço de classe compactada faz parte da área de classe e o parâmetro MaxMetaspaceSize
limita o tamanho de toda a área de classe, não apenas o espaço de classe compactado. Para limitar o "espaço de classe compactado", o parâmetro CompressedClassSpaceSize
é usado.
A partir daqui :
Se UseCompressedOops
estiver ativado e UseCompressedClassesPointers
for usado, duas áreas logicamente diferentes da memória nativa serão usadas para metadados de classe ...
Uma região é alocada para esses ponteiros de classe compactados (as compensações de 32 bits). O tamanho da região pode ser definido com CompressedClassSpaceSize
e é 1 gigabyte (GB) por padrão ...
MaxMetaspaceSize
se aplica à soma do espaço de classe compactado confirmado e do espaço para os outros metadados da classe
Se o parâmetro UseCompressedOops
estiver UseCompressedOops
e UseCompressedOops
usado, duas áreas logicamente diferentes da memória nativa serão usadas para os metadados da classe ...
Para ponteiros compactados, uma área de memória é alocada (compensações de 32 bits). O tamanho dessa área pode ser definido por CompressedClassSpaceSize
e, por padrão, é de 1 GB ...
O parâmetro MaxMetaspaceSize
refere-se à soma da área do ponteiro compactado e da área para outros metadados da classe.
- Thread: A memória usada pelos threads na JVM. A função do número de threads em execução.
- Cache de código: a memória usada pelo JIT para executá-lo. Uma função do número de classes que foram carregadas. Limitado a
ReservedCodeCacheSize
. Você pode reduzir a configuração do JIT, por exemplo, desativando a compilação em camadas. - GC (coletor de lixo): armazena os dados usados pelo coletor de lixo. Depende do coletor de lixo usado.
- Símbolo: armazena caracteres como nomes de campos, assinaturas de métodos e seqüências de caracteres internas. O uso excessivo da memória de caracteres pode indicar que as linhas estão muito internas.
- Interno: armazena outros dados internos que não estão incluídos em nenhuma das outras áreas.
DiferençasComparado a um heap, a memória fora do heap muda menos sob carga. Assim que o aplicativo carregar todas as classes que serão usadas e o JIT estiver totalmente aquecido, tudo entrará em um estado estável. Para ver uma diminuição no uso do
espaço da classe Compactado , o carregador de classes que carregou as classes deve ser removido pelo coletor de lixo. Isso era comum no passado quando os aplicativos eram implementados em contêineres de servlets ou servidores de aplicativos (o carregador de classes de aplicativos era removido pelo coletor de lixo quando o aplicativo foi removido do servidor de aplicativos), mas isso raramente acontece com as abordagens modernas da implantação de aplicativos.
Configurar JVMConfigurar a JVM para usar com eficiência a RAM disponível não é fácil. Se você executar a JVM com o parâmetro
-Xmx16M
e esperar que não mais que 16 MB de memória sejam usados, você terá uma surpresa desagradável.
Uma área interessante da memória da JVM é o cache de código JIT. Por padrão, o HotSpot JVM usará até 240 MB. Se o cache de código for muito pequeno, o JIT pode não ter espaço suficiente para armazenar seus dados e, como resultado, o desempenho será reduzido. Se o cache for muito grande, a memória poderá ser desperdiçada. Ao determinar o tamanho de um cache, é importante considerar seu efeito no uso e no desempenho da memória.
Ao executar em um contêiner Docker, as versões mais recentes do Java
agora estão
cientes das limitações de memória do contêiner e estão tentando redimensionar a memória da JVM adequadamente. Infelizmente, muita memória é frequentemente alocada fora do heap e não é suficiente no heap. Suponha que você tenha um aplicativo em execução em um contêiner com 2 processadores e 512 MB de memória disponível. Você deseja lidar com mais carga de trabalho e aumentar o número de processadores para 4 e a memória para 1 GB. Como discutimos acima, o tamanho do heap geralmente varia com a carga e a memória fora do heap muda significativamente menos. Portanto, esperamos que a maioria dos 512 MB adicionais seja alocada para o heap para lidar com o aumento de carga. Infelizmente, por padrão, a JVM não fará isso e distribuirá memória adicional de maneira mais ou menos uniforme entre a memória na pilha e fora da pilha.
Felizmente, a equipe do CloudFoundry possui amplo conhecimento de alocação de memória na JVM. Se você baixar aplicativos para o CloudFoundry, o pacote de compilação aplicará automaticamente esse conhecimento a você. Se você não estiver usando o CloudFoudry ou gostaria de entender mais sobre como configurar a JVM, é recomendável ler a
descrição da terceira versão da
calculadora de memória do
Java buildpack .
O que isso significa para o SpringA equipe do Spring gasta muito tempo pensando no desempenho e no uso da memória, considerando a possibilidade de usar a memória no heap e fora dele. Uma maneira de limitar o uso de memória fora do heap é tornar as partes da estrutura o mais versáteis possível. Um exemplo disso é usar o Reflection para criar e injetar dependências nos beans do seu aplicativo. Com o uso do Reflection, a quantidade de código de estrutura que você usa permanece constante, independentemente do número de beans em seu aplicativo. Para otimizar o tempo de inicialização, usamos o cache na pilha, limpando-o após a conclusão da inicialização. A memória da pilha pode ser facilmente limpa pelo coletor de lixo para fornecer mais memória disponível ao seu aplicativo.
Tradicionalmente, estamos aguardando seus comentários sobre o material.