Estratégias de Otimização de Recursos em Cloud pela JVM

Thyago Mininelli
luizalabs
Published in
8 min readJun 21, 2021

Atualmente muito se tem utilizado a solução Cloud pelas aplicações corporativas. Sabemos que a solução Cloud se dá ao fato de servidores serem virtualizados em ambientes com uma facilidade de escalabilidade horizontal. Desde então surgiu o conceito de Contêiner, onde é disponibilizado uma parte de recursos da máquina para rodar o sistema operacional e suas aplicações. Isso consegue distribuir melhor a utilização de recursos e melhorar o gasto financeiro.

Algumas linguagens de programação, como Python e JavaScript, conseguem trabalhar com esse ambiente com mais facilidade pelo fato de serem multiplataforma e necessitarem de menos recursos. De alguns anos pra cá estamos vendo o crescimento dessas linguagens no desenvolvimento de aplicações nas corporações. No ranking da IEEE Spectrum vemos o Python como a linguagem mais utilizada no mundo no ano de 2020:

1º. Python

2º. Java

3º. C

4º. C++

5º. JavaScript

Fonte: https://cio.com.br/carreira/python-domina-ranking-de-linguagens-de-programacao-de-2020/

Entretanto, como o vemos no ranking, o Java é umas das linguagem mais utilizadas no desenvolvimento de sistemas corporativos. Sabemos que a linguagem oferece várias soluções, como bibliotecas eficientes e utilização de vários frameworks que oferecem à aplicação segurança, integrações eficientes, manipulação de dados, entre outras.

Mas, na última década, vimos uma certa demora na adaptação do Java para utilizar recursos em Cloud. Tanto que, avaliações evidenciam que é preciso um tempo alto para inicializar suas aplicações e alta alocação de recursos, comparado com outras linguagens. No caso, a utilização de recursos é na memória da máquina virtual. A JVM, Java Virtual Machine, tem a sua estrutura bem definida, entretanto, bem questionada no gerenciamento de memória. E, as suas bibliotecas e frameworks utilizam muitos recursos para serem instanciados. Suas instâncias são para oferecer as muitas soluções que as mesmas provêm.

JVM

A Java Virtual Machine, JVM, tem como sua definição a separação da utilização de recursos, ou seja, existem partes da JVM que alocam recursos para determinada instância. São elas:

HeapSpace: é a localização na JVM utilizada para as instâncias runtime. Elas são ocupadas no momento da execução da aplicação com a criação das instâncias. Nessa parte o trabalho do Garbage Collector é fundamental para finalizar essas instâncias e devolver os recursos utilizados.

Metaspace: substituindo a antiga PermGen, é a localização na JVM que lida com os metadados das classes, incluindo os métodos estáticos, variáveis primitivas e referências aos objetos estáticos. Alocando recursos no momento de inicialização, esse novo espaço da JVM pode ser configurado no seu limite máximo e mínimo. Outro ponto importante é o trabalho do Garbage Collector que é acionado automaticamente para que o uso de memória seja mais otimizado.

Por mais que houvesse atualizações do Java para a utilização dos recursos pela JVM, ainda é um ponto crítico para aplicações em nuvem. A maneira como ela pode ser utilizada é fundamental para melhorar o desempenho da aplicação, assim como os custos em cloud.

Conseguimos identificar a utilização desses recursos através das ferramentas como VisualVM, Java Flight Recorder, entre outras. Com elas podemos subir uma aplicação e realizar testes e monitorar o consumo de memórias e suas respectivas partes.

Novas versões do Java

As novas versões de bibliotecas foram lançadas com objetivo de realizar um trabalho mais eficiente para aplicação e a utilização dos recursos em Cloud. Levando em consideração também a maneira como a JVM trabalha, como mencionado acima.

Podemos apontar algumas soluções interessantes lançadas nas novas versões como: Lambda, Async, Stream e MultiThread.

Arquiteturas e Frameworks

Como o tema principal deste artigo é a solução em nuvem, é importante escrever um pouco sobre isso. A arquitetura Cloud surgiu com a necessidade de melhorar a utilização de recursos de uma máquina física através de virtualizações e separação de recursos. Desta forma melhorou a utilização destes recursos, as empresas conseguiram melhorar a gestão de infraestrutura e diminuir os custos e os problemas de escalabilidade.

Entretanto, não basta migrar para uma solução cloud, é necessário implementar no código novas bibliotecas para melhorar a performance, pensarmos em boas soluções de arquitetura e utilização eficiente dos frameworks.

Quando falamos de arquitetura de software um assunto vasto nos vem em mente, discussões aprofundadas para prover o melhor entendimento para o sistemas, mas, uma coisa é clara, um dos pontos que deve ser levado em consideração na escolha de um melhor desenho é o consumo de recursos. Tendo em vista esse ponto, podemos levar em consideração a arquitetura distribuída, o objetivo dessa arquitetura é a capacidade de distribuir componentes em várias máquinas ou computadores (chamados de nós) de forma independente e organizada, a fim de distribuir as responsabilidades entre nós diferentes. Sabemos que ao distribuir as responsabilidades precisamos de poucos recursos e a escalabilidade pode ser melhor gerenciada.

Soluções de integração e arquitetura distribuída são concepções para este tipo de cenário. Podemos levar em consideração bancos NoSQL, como Redis, Cassandra, que são leves e necessitam de poucos recursos. Outros também são o trabalho de filas e tópicos com Kafka ou RabbitMQ para deixar os processos assíncronos. Além de diminuir o tempo de integração e comunicação entre módulos, leva em consideração, também, a utilização de recursos.

Atualmente, os frameworks estão lançando novas soluções e sendo criados para isso. Podemos falar do Quarkus, framework lançado pela RedHat totalmente voltado para escrita em nuvem com bibliotecas que trabalham com imagens nativas, adaptadas para container e que precisam de menos recursos para desempenhar determinadas funções.

Mesmo sendo muito utilizado pela comunidade Java e, ainda, não apresentando eficiência para a solução Cloud, o Spring Framework está lançando a sua versão para nuvem. Nesta versão tem bibliotecas que começam a repensar em melhorar a utilização de recursos.

Estratégias para Otimizar recursos

Com o passar do tempo percebemos que não existe uma bala de prata, mas sim, conhecer melhor a linguagem que trabalhamos, frameworks que utilizamos, modelos de arquiteturas e implementações corretas. Isso se faz necessário para desmistificar uma idealização e levar em consideração diálogos, leituras e troca de experiências. Abaixo pontos que devemos levar em consideração:

- Processos assíncronos: Permitem eliminar o encadeamento de comunicação, tanto de recursos internos como externos. Com processos assíncronos as threads não precisam ficar abertas aguardando a conclusão de vários processos, principalmente quando acontece a integração de vários recursos e microsserviços. Ao adotar a comunicação assíncrona permitimos que determinada requisição seja realizada de maneira simples e curta, evitando alocação de recursos por grandes períodos.

- Recursos adaptados para container: Nos últimos tempos vimos o lançamento de recursos totalmente focados para solução em nuvem. Em Bancos de Dados temos por exemplo o Cassandra e o Redis. Em comunicação de filas e tópicos temos o Kafka. A utilização destes recursos favorece o melhor desempenho e otimização de recursos da nuvem. Recursos que são serviços em cloud.

- Boas práticas de codificação: Esse ponto é bastante importante pois sabemos que a cada instância de objeto uma parte da memória é alocada. Com isso, quanto mais instâncias forem criadas, mais recursos serão alocados. Outro ponto importante são os métodos curtos para que o Garbage Collector seja executado com mais frequência. Métodos longos fazem com que um objeto utilize durante longo período de tempo os recursos da JVM. Declarar variáveis locais ao invés de globais se faz necessário principalmente pelo fato que comentamos anteriormente, variáveis locais conseguem ser removidas e diminuem a utilização de recursos.

- Imagem Nativa: Uma das estratégias para otimizar recursos é trabalhar com imagens nativas. Atualmente no Java podemos trabalhar com imagens nativas utilizando o GraalVM, nele as aplicações não são executadas em cima de uma JVM e sim em componentes de execuções. Esses componentes de execuções incluem gerenciamento de memória, classes de bibliotecas, agendamento de threads, assim por diante. O GraalVM pode fornecer benefícios executando-os mais rapidamente, fornecendo uma compilação Just In Time (JIT) mais rápida, permitindo que você compile antecipadamente o código Java para um executável autônomo, chamado de imagem nativa. Com isso, a aplicação resultante tem um tempo de inicialização mais rápido e menor sobrecarga de memória de tempo de execução em comparação com uma JVM.

- Imagem em Container: A imagem em container corresponde ao empacotamento de dependências, bibliotecas e configurações necessárias para a sua aplicação executar de maneira isolada e eficaz. A utilização de imagens em containers no Java segue uma tendência na tecnologia da Informação nos ambientes Cloud. Os benefícios podem ser apresentados desde a otimização de recursos, separação de responsabilidades, processos de entregas mais eficientes (DevOps) e disponibilidade e gerenciamento da aplicação.

Conclusão

Embora haja muitos profissionais de desenvolvimento de sistemas na linguagem Java é importante pautar nas discussões sobre JVM e utilização de seus recursos. Se antigamente esse tema era relevante, atualmente ficou essencial com a utilização da nuvem. Implementações em virtualização e container são soluções imprescindíveis para muitos problemas, dentre eles a escalabilidade. Porém, a utilização de seus recursos e o custo são coisas que, se não gerenciadas corretamente, causarão problemas em médio ou longo prazo.

Como vimos no artigo, o entendimento da Máquina Virtual Java e ferramentas para avaliar o consumo deverão fazer parte do dia-a-dia dos profissionais de tecnologia da informação. Por isso, a escolha das versões de JDK e afins mais recentes são ideais, pois, já permitem ter um melhor trabalho quando falamos em cloud.

Entretanto, não pode parar por aí. Nos últimos anos novas versões de bibliotecas da linguagem vêm sendo lançadas com objetivo, também, de melhorar o desempenho em Cloud. E, paralelamente, frameworks especializados nesta solução estão surgindo para ajudar nisso. Podemos levar em consideração alguns exemplos: o Quarkus, GraalVM, e o mais recente nessa versão, o Spring.

Pensar dessa forma é um fator motivante para a tomada de decisão das melhores estratégias de otimização na utilização de recursos. A partir do momento que entendemos a linguagem, suas bibliotecas e os frameworks, entramos no outro ponto que é a arquitetura do sistema e a sua codificação. Novos recursos de banco de dados e filas são lançados e criados já no modelo de container e nuvem, como o Kafka, Redis, etc. E além de melhorar a codificação com boas práticas e código enxuto, podemos falar também que avaliação de entrega de código pelos merges requests ou pull requests são pontos importantes para melhorar a codificação.

Podemos concluir que o objetivo deste artigo foi discutir um ponto importante na comunidade e, principalmente, dentro das equipes de desenvolvimento, o fato de entender que esse tema não pode ser apenas um assunto para os times de infraestrutura e DevOps. Compreender e levar em consideração a utilização dos recursos da JVM na entrega e implantação de sistemas e aplicações é assunto que deve fazer parte do cotidiano dos times de desenvolvimento e serem considerados, também, fatores nas tomadas de decisões.

--

--

Thyago Mininelli
luizalabs

Código e café no sangue. Pensando em soluções tecnológicas para proporcionar inovações.