Como a JVM funciona por baixo dos panos, um conceito de cada vez.
Como branches condicionais sempre compilam para o inverso da condição do source, por que == em objetos é if_acmpeq (identidade, nunca equals()), por que um switch compila para tableswitch ou lookupswitch dependendo da densidade dos labels, por que return é seis instruções específicas de tipo em vez de uma, e por que um bloco synchronized custa uma exception table gerada pelo compilador que um método synchronized nunca precisa.
Por que 'new' é alocar-e-depois-construir como duas instruções separadas, como o acesso a campos e arrays se divide em famílias distintas de opcodes, e por que o JVM tem cinco instruções invoke* diferentes em vez de uma — incluindo por que o javac moderno não emite mais invokespecial para chamadas de método privado.
Como funciona a aritmética em stack machine do bytecode — os prefixos i/l/f/d, por que comparações de float e double precisam de dois opcodes para tratar NaN corretamente, e por que conversões de estreitamento como d2i e i2b perdem dados silenciosamente em vez de lançar exceção.
Como o JVM transforma bytecode em código nativo enquanto o programa já está rodando, por que ele espera o código ficar "quente" antes de compilá-lo, e como a tiered compilation combina um compilador que inicia rápido (C1) com um mais lento e mais inteligente (C2).
Como o javac transforma código-fonte Java em um arquivo .class binário estruturado — magic number e versão no header, a tabela de referências simbólicas do constant pool, access flags, como fields e methods são organizados para o JVM carregar e executar, e os códigos de descriptor de uma letra (B/C/D/F/I/J/S/Z, L...;, [) que codificam cada tipo no arquivo.
Por que trocar RAM por CPU costuma ser a decisão economicamente correta dado o preço de RAM por núcleo em hardware de nuvem e de consumo, como medir a taxa real de alocação e o custo de CPU do GC em vez de chutar, por que benchmarks ingênuos de memória entre linguagens enganam, e o que o dimensionamento adaptativo de heap propõe automatizar em seguida.
Como funciona o footprint total de memória do JVM (heap mais memória nativa), por que memória reservada não é o mesmo que memória de fato em uso, e como quebrar isso em detalhes com Native Memory Tracking.
Como compact strings reduzem pela metade o custo de heap de uma String média, quando vale a pena ligar a deduplicação de strings do G1, e por que a otimização de concatenação do próprio compilador costuma bater uma cadeia de StringBuilder escrita à mão.
Como usar histogramas de heap e heap dumps para descobrir o que está realmente consumindo memória, e como interpretar os diferentes tipos de OutOfMemoryError em vez de simplesmente aumentar o heap e torcer.
Por que os coletores geracionais e de compactação da JVM nunca liberam um objeto como malloc/free ou a contagem de referências fazem, e como esse design transforma o custo de CPU do GC em uma função direta da folga de heap que você controla com -Xmx.
Como os colored pointers e a load barrier do ZGC mantêm as pausas abaixo de um milissegundo movendo objetos de forma concorrente, o que o ZGC geracional mudou entre o JDK 21 e o 24, e por que um allocation stall (e não uma pausa) é o modo de falha real do ZGC a monitorar.
Como o G1 organiza o heap em regiões em vez de gerações fixas, por que uma alocação de 1MB+ pode pular o eden e cair direto na old generation, e como ler uma linha de log de GC o suficiente para distinguir uma young collection saudável de uma evacuation failure.
Como os collectors geracionais do JVM fazem trade-off entre pause time, throughput e overhead de CPU, e qual collector o JVM realmente usa por padrão hoje versus o que um livro de 2020 assume.
Por que coleções não são sincronizadas por padrão, por que dimensionar uma coleção antecipadamente importa, e por que uma cadeia de operações de Stream processa muito menos dados do que parece.
Por que os frameworks de servidor Java migraram de uma thread por conexão para selectors NIO e padrões de resposta assíncrona, e por que as virtual threads tornaram a maior parte dessa cerimônia desnecessária de escrever à mão.
Por que dimensionar um ThreadPoolExecutor para trabalho compute-bound significa igualar a quantidade de cores, dimensionar para trabalho I/O-bound significa ultrapassar isso de longe, e qual metade desse problema as virtual threads realmente resolveram.
Por que um microbenchmark caseiro do tipo "cronometrar um loop" costuma mentir sobre performance Java, e como o JMH (Java Microbenchmark Harness) existe especificamente para evitar essas armadilhas.
Como a gravação baseada em eventos do JFR, com overhead abaixo de 1%, permite fazer profile de um JVM de produção ao vivo continuamente, e como controlá-lo inteiramente pela linha de comando.