O básico sobre carregamento de classes pela Java JVM e manipulação do ClassLoader
Tags: braziliandevs, java
Published: 2023-05-09 02:20:29



> TL;DR: A JVM gerencia o carregamento de classes em tempo de execução através de um processo que envolve carregamento, vinculação e inicialização. Os ClassLoaders desempenham um papel fundamental nesse processo, carregando classes dinamicamente na memória da JVM. Existem três tipos principais de ClassLoader: Bootstrap, Extension e System/Application. Ao criar um ClassLoader personalizado, é possível substituir a implementação de uma classe em tempo de execução. Compreender o funcionamento dos ClassLoaders e o processo de carregamento de classes é crucial para o desenvolvimento eficiente e flexível de aplicações Java.


## Introdução

A Máquina Virtual Java (JVM) é um componente crucial do ecossistema Java. Uma das suas principais responsabilidades é gerenciar o carregamento das classes em tempo de execução. Para compreender o funcionamento da JVM e aproveitar ao máximo os recursos do Java, é fundamental conhecer o processo de carregamento de classes e o papel dos ClassLoaders. Neste artigo, exploraremos o processo de carregamento das classes pela JVM, detalharemos o fluxo do ClassLoader e apresentaremos exemplos de como substituir uma implementação de classe por outra em tempo de execução.

## Carregamento de classes pela JVM

O carregamento de classes pela JVM é o processo no qual a máquina virtual lê as informações das classes compiladas (arquivos .class) e as transforma em objetos internos. Esse processo envolve três etapas: carregamento, vinculação e inicialização.

1. **Carregamento**: Durante essa fase, o ClassLoader encontra e carrega a representação binária da classe (arquivo .class) na memória da JVM. 
2. **Vinculação**: A etapa de vinculação verifica se a classe carregada está corretamente formatada e resolve quaisquer referências a outras classes. Esta fase inclui três subetapas: verificação, preparação e resolução.
3. **Inicialização**: Nesta etapa, a JVM executa o código de inicialização estática da classe (atribuições de variáveis estáticas e blocos estáticos) e a classe fica pronta para ser usada.

## O papel dos ClassLoaders

Os ClassLoaders são componentes fundamentais do processo de carregamento de classes. Eles são responsáveis por carregar as classes dinamicamente na memória da JVM em tempo de execução. Existem três tipos principais de ClassLoader no Java:

1. **Bootstrap ClassLoader**: Carrega as classes básicas do Java (java.lang e outras classes do pacote rt.jar).
2. **Extension ClassLoader**: Carrega as classes de extensão do Java (classes do diretório lib/ext ou aquelas especificadas pela propriedade java.ext.dirs).
3. **System/Application ClassLoader**: Carrega as classes da aplicação a partir do classpath.

Os ClassLoaders seguem o princípio da delegação, ou seja, quando um ClassLoader recebe uma solicitação para carregar uma classe, ele delega essa tarefa ao seu ClassLoader pai. Se o ClassLoader pai não encontrar a classe, o ClassLoader atual tentará carregar a classe por conta própria.

## Substituindo a implementação de uma classe em tempo de execução

Podemos substituir a implementação de uma classe em tempo de execução criando um ClassLoader personalizado. Por exemplo, vamos substituir a implementação da classe `MyClass`:

```java
public class MyClass {
    public void printMessage() {
        System.out.println("Mensagem original");
    }
}
```
Crie um novo ClassLoader personalizado que substituirá a implementação da classe `MyClass`:

```java
public class MyCustomClassLoader extends ClassLoader {
    public MyCustomClassLoader(ClassLoader parent) {
        super(parent);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (!"MyClass".equals(name)) {
            return super.loadClass(name);
        }

        byte[] classData = ... // Carregue os bytes da nova implementação da classe MyClass a partir de um arquivo, por exemplo

        return defineClass(name, classData, 0, classData.length);
    }
}

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClass myClass = new MyClass();
        myClass.printMessage(); // Output: Mensagem original

        MyCustomClassLoader customClassLoader = new MyCustomClassLoader(Main.class.getClassLoader());
        Class<?> myCustomClass = customClassLoader.loadClass("MyClass");
        Object myCustomObject = myCustomClass.newInstance();

        Method printMessageMethod = myCustomClass.getMethod("printMessage");
        printMessageMethod.invoke(myCustomObject); // Output: Mensagem substituída
    }
}
```

## Conclusão

Neste artigo, exploramos o processo de carregamento de classes pela JVM e o papel dos ClassLoaders. Além disso, mostramos como criar um ClassLoader personalizado para substituir a implementação de uma classe em tempo de execução. Compreender o funcionamento dos ClassLoaders e o processo de carregamento de classes é fundamental para aproveitar ao máximo os recursos do Java e desenvolver aplicações eficientes e flexíveis.


## Referências

Documentação oficial do Java: [Class Loaders](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html)
Documentação oficial do Java: [The Java® Virtual Machine Specification](https://docs.oracle.com/javase/specs/jvms/se7/html/index.html)
Note que os links de referências acima são para a especificação do Java SE 7. É recomendável verificar a documentação correspondente à versão específica do Java que você está utilizando.