Aleksey Shipilëv, desenvolvedor do OpenJDK na RedHat, apresentou uma nova proposta de JEP para criar um Garbage Collector (GC) não operacional; isto é, um GC que não retém memória. Este coletor tem como objetivo ajudar os implementadores e pesquisadores da JVM e, em menor grau, mas talvez interessante também para o público que desenvolve aplicações de grande desempenho que geram pouco ou nenhum lixo.
Se esta JEP for adiante, o novo GC pode ser disponibilizado junto com os GCs existentes e não terá efeito a menos que seja explicitamente ativado.
O GC e o desempenho no Java sempre foram tópicos complexos de serem tratados, e para abordar o assunto, o InfoQ conversou com os Java Champions e especialistas em desempenho Martijn Verburg e Kirk Pepperdine. Também conversamos com Remko Popma, que lidera as mudanças no Log4j para não usar GC, sobre como este objetivo pode ser alcançado.
Verburg e Popma afirmaram que, podemos ter uma aplicação executando com um GC, ou um Epsilon GC como é chamado, podendo ser um GC para desenvolvedores ou pesquisadores de desempenho. O Epsilon GC pode servir como uma variável de controle que mensura o desempenho dos outros GCs. Por exemplo, uma aplicação pode executar em um GC sem operação para uma sobrecarga reduzida (excluindo outras considerações como alocação de memória e controle de mutação). Se a mesma aplicação executa com a mesma carga de trabalho, mas com a configuração diferente de um algoritmo de GC, a diferença em desempenho indica o impacto que o GC tem na aplicação. Isso pode ajudar os desenvolvedores e pesquisadores de desempenho, a entenderem o comportamento do GC de maneira mais isolada.
Isto é um grande passo que permitirá benchmarkings de várias partes da JVM com mais acurácia (tal como, nos compiladores JIT C1/C2, a possibilidade para trocar para o Graal, etc). Isto realmente proporcionará longevidade extra a JVM". Martijn Verburg
Por outro lado, as aplicações de alto desempenho poderiam se beneficiar do Epsilon GC. Existem poucas aplicações e bibliotecas, como o Log4j, que foram implementadas de forma a não produzem lixo e, portanto, não precisam de um coletor de lixo; para este tipo de aplicações, o desempenho pode ser melhorado removendo a sobrecarga do GC. No entanto, Popma destaca que a construção de uma biblioteca que pode ser executada com o Epsilon GC "pode aumentar significativamente o esforço de engenharia, pois será necessário garantir que a memória da aplicação seja gerenciada cuidadosamente, o suficiente para que não se acabe", e mesmo assim, uma avaliação de risco e benefício deve ser feito para verificar se os ganhos obtidos ao escolher um GC não operacional são consistentes com a dificuldade de alcançar um estado zero de lixo.
Pode parecer difícil imaginar como um aplicativo pode ser escrito para não produzir lixo, no entanto, apesar do tópico ser muito mais complexo do que o que poderia ser explicado neste artigo, pode ser mais fácil de entender levando em consideração as seguintes considerações:
- A memória é gerenciada por meio de dois mecanismos diferentes na JVM: o heap e a stack; É por isso que existem dois erros diferentes em relação à falta de memória (OutOfMemoryError e StackOverflowError). A memória colocada no stack só é visível pela thread atual e durante a execução do método atual; portanto, quando a thread atual deixa o método atual, esta memória é liberada automaticamente sem a necessidade de um GC. A memória no heap, no entanto, é acessível por todo a aplicação, o que significa que um GC precisa verificar quando uma parte da memória não está mais em uso e pode ser recuperada.
- A atribuição de tipos primitivos sempre vai na stack e, portanto, não representa nenhuma pressão para o GC. Se alguém fosse escrever código usando principalmente tipos primitivos, haveria menos objetos para o GC cuidar.
- Produzir nenhum lixo não é o mesmo que não produzir nenhum objeto; os objetos ainda podem ser criados sem a necessidade de um GC para cuidar deles:
- Uma aplicação ou biblioteca poderia produzir uma série de objetos na inicialização e depois reutilizá-los constantemente, mas isso exige que os desenvolvedores compreendam muito bem a memória da aplicação;
- Às vezes, o compilador pode entender que um objeto específico não será usado fora de um método; isso é conhecido como análise de fuga. Quando o objeto é encontrado não sairá do método, ele pode ser alocado na stack ao invés da heap e, portanto, eliminado automaticamente assim que o método atual terminar.
Embora tudo isso seja possível, Pepperdine comenta que esta é uma maneira diferente de escrever código, que implica em perder muitos dos benefícios que o Java oferece, e Verburg também afirma que o gerenciamento de memória é um dos principais motivos pelo qual o Java fez sucesso na indústria. Além disso, precisamos ter em mente que, apesar do seu nome, o coletor de lixo não tem apenas a tarefa de recuperar a memória não utilizada, mas também alocar novos blocos, e o Epsilon GC precisará fazer isso. Pepperdine usa este argumento para sugerir que, pelo menos em teoria, não haveria diferença significativa no desempenho entre ter o Epsilon GC e ter qualquer outro algoritmo de GC monitorando algum ponto onde não há coleta de lixo.
No entanto, mesmo considerando todas essas ressalvas, tanto Pepperdine quanto Verburg confirmaram que existe uma pequena audiência para a qual esse tipo de comportamento pode ser muito útil, mas o fato desta audiência ser pequena, segundo Kirk, traz em dúvida sua utilidade. A grande maioria das aplicações precisam recuperar a memória em algum ponto e, portanto, precisa de um coletor de lixo funcional.
"Os tempos de pausa do GC são razoáveis, e simplesmente não são um problema para a maioria das aplicações, então porque desistir de todos os benefícios do Java por uma vitória de desempenho questionável". Kirk Pepperdine
Pepperdine também lembra que cada novo recurso adiciona custos de manutenção ao OpenJDK e, portanto, os desenvolvedores do OpenJDK devem levar em consideração o todo, quando algo novo é adicionado. A Oracle reduziu o número de GCs disponíveis precisamente para reduzir os custos de manutenção e, portanto, adicionar um GC que só será útil para uma pequena porcentagem de usuários pode não representar o investimento certo. Parece que Shipilëv considerou esses pontos ao propor a JEP, e a análise preliminar parece indicar que a sobrecarga seria mínima neste caso, a julgar pelo conteúdo da proposta e pelo protótipo que foi fornecido. Na verdade, tanto Pepperdine quanto Verburg ressaltaram que, dada a experiência de Shipilëv, o fato dele estar liderando essa iniciativa é razão suficiente para ser otimista sobre isso.
Por outro lado, Pepperdine também enfatizou o fato de que, embora o OpenJDK seja a implementação de referência da JVM, não há exigências de conformidade com coletores de lixo, portanto qualquer fornecedor pode implementar seus próprios algoritmos e ainda serem totalmente compatíveis com o Java. Isso poderia causar uma divisão de opiniões entre o público: embora alguns possam pensar que implementar algoritmos para nichos de mercado pode ser algo mais apropriado para versões comerciais da JVM, outros podem considerar isso uma adição muito útil para o OpenJDK.
@shipilev Zing actually has a no GC option. It's very useful for testing. Some people would love that option on OpenJDK.
— Nitsan Wakart (@nitsanw) February 12, 2017
Popma também recomendou considerar o uso de JVMs comerciais quando o desempenho se tornar crítico, uma vez que o custo de uma licença de um desses produtos poderia, em geral, ser inferior ao custo de ter equipe de engenharia selecionando e ajustando um algoritmo de GC específico. No entanto, mesmo que alguém tivesse que usar apenas as tecnologias OpenJDK, tanto Popma quanto Verburg mencionaram o GC Shenandoah, atualmente em desenvolvimento, que visa produzir tempos de pausa ultra baixos para heaps muito grandes (100 GB ou mais). Em ambos os casos, o consenso entre os especialistas parecia ser que, quando se trata de desempenho da aplicação, um algoritmo de GC cuidadosamente selecionado será quase sempre melhor do que nenhum GC.
A proposta ainda está no começo e precisa ser revisada antes de virar uma JEP oficial. Quando e se isto ocorrer, uma versão alvo do JDK será eventualmente adicionado. Embora neste momento não seja possível especular qual será a versão alvo, Verburg sugere que uma expectativa razoável para o Epsilon GC ficar pronto é no Java 10 ou 11. Em qualquer caso, pelo menos ajudaria a entender como a interface de um GC deveria ser, contribuindo para uma JVM mais modular.