Qual é o problema do Java?

Foi uma pergunta que fizeram no grupo de um projeto de que faço parte. A solução do projeto está sendo desenvolvida em C e C++. Então, muitos responderam com críticas sobre a linguagem Java. O que eu já esperava, pois normalmente quem trabalha com linguagens mais perto do hardware não curte Java. Eu também não me identifico com a linguagem, mas respeito quem se gosta e trabalha todos os dias nela.

O Java meio que popularizou o conceito de "Escreva uma vez e rode aonde quiser" (WORA - Write Once and Run Everywhere) nos anos 90. Esse é um modelo de sonho antigo que teve origem ainda nos anos embrionários da programação de software. Um dos principais méritos do Java, eu acho, é que a sua máquina virtual (JVM) apresentou uma solução bem mais otimizada do que o interpretador do Python -- que surgiu em 1991, quatro anos antes do Java -- conseguia atingir. A solução da JVM meio que se tornou um case de sucesso para que linguagens interpretadas pudessem se basear nela para se otimizar.

Bem, agora respondendo a pergunta. Qual é problema que vejo sobre o Java?

Para mim, o principal problema da linguagem Java acaba sendo exatamente a sua principal vantagem: a abstração hierárquica de objetos. São muitas classes com hierarquias extensas e, além disso, como no Java praticamente tudo se trata de objeto, por baixo dos panos ele faz muita alocação de memória em tempo de execução. Isso tem um custo direto, que é o desempenho.

Essa perda de performance fica clara quando olhamos para o hardware. Do ponto de vista do cache do processador, o ideal é que os dados fiquem armazenados de forma contígua e alinhados nas cache lines. E aí, quando o processador busca esses dados, ele consegue copiar essa cadeia para as linhas do cache. Como ele encontra o que precisa diretamente nessa sequência, a execução continua sem interrupções e, portanto, não se perde tempo (ciclos) buscando na memória principal.

O problema é que a alocação dinâmica tende a espalhar os dados na memória de forma não contígua. Só que isso não é exclusividade do Java; qualquer alocação dinâmica em heap acaba criando esses blocos de memória não sequenciais. A questão é que no Java praticamente tudo é alocado dinamicamente no heap.

Nesse processo, o que acontece é que, quando um aplicativo solicita alocação de memória, o gerenciador (e indiretamente o kernel) busca blocos disponíveis que atendam ao tamanho pedido, o que ao longo do tempo gera fragmentação. Para o processador, quando os dados estão espalhados, perde-se a previsibilidade espacial, dificultando que o mecanismo de prefetch traga de forma eficiente os dados adjacentes que serão processados em seguida.

É por essa razão que eu digo que a principal vantagem é também o principal problema de desempenho: os objetos em Java carregam muita estrutura e metadados das classes pai. Muita coisa acaba indo para o cache do processador, só que, na maioria das vezes, são dados frios que não têm utilidade naquele momento exato da computação.

Para ver esse impacto do hardware na prática, basta comparar como o processador lida com diferentes estruturas de dados: ao percorrer uma lista encadeada, ele normalmente leva mais tempo do que ao varrer um vetor (array). Se, por exemplo, formos somar uma sequência de números {1, 2, 3, ..., N}, sendo uma representada por uma lista encadeada e a outra por um array, o processamento no array supera em velocidade porque todos os dados estão contíguos na memória. Isso não invalida ou torna a lista encadeada inútil: dependendo do contexto, a alocação dinâmica dela é melhor, principalmente quando não sabemos o tamanho máximo desse conjunto de dados.

A diferença é que, em linguagens como C e C++, o programador tem muito mais flexibilidade na abordagem a ser escolhida. Elas dão a opção de alocar dinamicamente na heap quando necessário, mas também permitem alocar tudo de forma estática ou automática na stack em tempo de compilação, garantindo um acesso muito mais contíguo e previsível.

Tenho colegas que não curtem C ou C++ e são programadores Java há muitos anos, especialistas no assunto e levando bem a carreira.

No fim das contas, o mais importante num projeto é escolher as ferramentas e as linguagens que melhor se adequam aos requisitos e aos problemas que a gente precisa resolver.