Learn · concepts
O que é um carregador de classe Java?
Um carregador de classes encontra e carrega classes Java para a JVM em tempo de execução. Veja como eles funcionam, por que a hierarquia é importante e por que os mods dependem deles.
Um classloader é a parte do tempo de execução Java que encontra um arquivo de classe e o carrega na JVM. Sempre que seu código acessa um tipo pela primeira vez, a JVM pergunta a um classloader: "Encontre esta classe para mim". O carregador localiza o bytecode, lê-o e a JVM define a classe na memória. Este mecanismo é invisível em programas Java normais, mas é o ponto de fixação exato que os frameworks de mod usam para corrigir o Minecraft.
O que um classloader realmente faz
Um classloader transforma um nome de classe em uma classe utilizável dentro da JVM em execução. Você solicita um tipo, o carregador encontra o arquivo .class correspondente (ou o gera), verifica e o vincula, e a classe está pronta para uso.
O trabalho acontece em três etapas:
- Encontrar — O carregador localiza o bytecode para a classe nomeada (de um JAR, do disco, da rede ou gerado em tempo real)
- Verificar e vincular — A JVM verifica se o bytecode é válido e resolve as referências a outras classes
- Usar — A classe está pronta e seu programa pode instanciá-la
É por isso que os programas Java podem carregar código dinamicamente em tempo de execução que nunca foi mencionado em tempo de compilação. O nome da classe é suficiente para o carregador encontrá-lo.
A hierarquia de classloaders
Os classloaders são organizados em uma cadeia pai, e a maioria deles pergunta ao pai primeiro. Uma aplicação Java padrão tem três classloaders embutidos:
| Carregador | Cargas | Propósito |
|---|---|---|
| Bootstrap | Classes de tempo de execução Java Core | Garante que tipos essenciais como java.lang.Object sejam sempre os mesmos |
| Plataforma | Módulos da biblioteca padrão fora do core | Módulos e extensões Java |
| Aplicação | Classes próprias do seu programa | O classpath que seu programa especifica |
Quando o carregador de aplicação é solicitado por uma classe, ele normalmente delega para cima primeiro. Ele pergunta ao carregador de plataforma, que pergunta ao carregador bootstrap, que tenta carregá-la. Somente se nenhum pai puder carregá-la, o filho tenta. Este é o modelo de delegação, e ele impede que a mesma classe seja carregada duas vezes com nomes diferentes.
Identidade de classe e como funciona
Uma classe é identificada tanto pelo seu nome quanto pelo carregador que a definiu. Dois carregadores diferentes podem cada um carregar uma classe chamada com.example.MyClass, e a JVM as trata como dois tipos completamente diferentes. Isso pode parecer obscuro, mas é a base de tudo que o Java pode fazer de interessante.
Bootstrap Loader: java.lang.Object (the core Object)
Application Loader: java.lang.Object (if somehow re-loaded by app loader)
→ Two different types, same name
→ This breaks your program, which is why delegation prevents it
Carregadores de classe personalizados
Programas podem definir seus próprios carregadores de classe. Um carregador personalizado pode ler bytecode de qualquer lugar: um arquivo JAR, a rede, um banco de dados ou bytecode que ele gerou ou transformou em tempo real. É assim que:
- Sistemas de plugins carregam código dinamicamente sem saber dele em tempo de compilação.
- Servidores de aplicativos (Tomcat, JBoss) executam múltiplos aplicativos no mesmo JVM, cada um com seu próprio classloader.
- Mods do Minecraft corrigem classes do jogo durante o carregamento.
Por que isso importa para mods do Minecraft
Mods existem por causa do carregamento de classes personalizado. Um mod loader para Minecraft inicia o jogo através de seu próprio classloader personalizado para que possa ver e modificar as classes do jogo durante o carregamento. Esse ponto de interrupção é onde a mágica acontece.
Quando um loader fica entre o disco e o JVM, ele pode transformar uma classe logo antes do JVM defini-la:
Disk: obfuscated Minecraft class
↓
Mod Loader (custom classloader)
↓ [bytecode transformation happens here]
↓
JVM: defines the patched class
↓
Memory: game runs with mods active
Um mod pode reescrever um método do jogo, injetar novo comportamento, adicionar campos, tudo sem tocar no JAR do jogo no disco. A classe que o JVM executa é a que o framework do mod modificou. É exatamente assim que Fabric, Forge e outros mod loaders corrigem o Minecraft.
Carregamento preguiçoso
As classes não são todas carregadas na inicialização. O JVM carrega cada classe na primeira vez que ela é realmente usada. Isso é chamado de carregamento preguiçoso:
Program starts → JVM doesn't load String class yet
User calls System.out.println() → String is needed → Loader loads it
Isso significa que um classloader precisa apenas encontrar e carregar as classes que seu código realmente toca. Classes não utilizadas nunca entram na memória. É por isso que sua inicialização do Minecraft pode demorar um pouco (mods adicionam muitas classes), mas não congela para sempre.
FAQ
Na primeira utilização, e não na inicialização. A primeira vez que o seu código interage com um tipo, a JVM solicita a um carregador de classes que o encontre e defina. Isso é chamado de carregamento preguiçoso, e é por isso que o código não utilizado nunca entra na memória.
Um carregador de classes solicita ao seu pai que carregue uma classe antes de tentar fazê-lo sozinho. A cadeia de pais se estende até o carregador de inicialização. Isso impede que a mesma classe seja carregada duas vezes com o mesmo nome por carregadores diferentes.
Sim, se classloaders diferentes os definirem. A JVM identifica uma classe tanto pelo seu nome quanto pelo seu loader definidor, de modo que classes com o mesmo nome de loaders separados são tratadas como tipos distintos.
Interceptar classes durante o carregamento e transformá-las na memória. Um carregador personalizado fica entre o disco e a JVM, modificando o bytecode antes que a JVM defina a classe. É assim que os mods fazem patch no Minecraft sem modificar o arquivo jar do jogo.