Carregador de classes
É utilizado para fornecer bytecode compilado à JVM, que geralmente é armazenado em arquivos com extensão.class
, mas também pode ser obtido de outras fontes, por exemplo, baixado pela rede ou gerado pela própria aplicação. De acordo com a especificação Java SE, para executar o código na JVM, você precisa concluir três etapas:
-
carregando bytecode de recursos e criando uma instância da classe
Class
Isso inclui procurar a classe solicitada entre aquelas carregadas anteriormente, obter bytecode para carregar e verificar sua exatidão, criar uma instância da classe
Class
(para trabalhar com ela em tempo de execução) e carregar classes pai. Se as classes e interfaces pai não tiverem sido carregadas, a classe em questão será considerada não carregada. -
vinculação (ou vinculação)
De acordo com a especificação, esta etapa é dividida em mais três etapas:
- Verificação , a exatidão do bytecode recebido é verificada.
- Preparação , alocando RAM para campos estáticos e inicializando-os com valores padrão (neste caso, a inicialização explícita, se houver, ocorre já na fase de inicialização).
- Resolução , resolução de links simbólicos de tipos, campos e métodos.
-
inicializando o objeto recebido
aqui, ao contrário dos parágrafos anteriores, tudo parece claro sobre o que deve acontecer. Seria, claro, interessante entender exatamente como isso acontece.
- A classe deve estar totalmente carregada antes de ser vinculada.
- Uma classe deve ser totalmente testada e preparada antes de ser inicializada.
- Erros de resolução de link ocorrem durante a execução do programa, mesmo que tenham sido detectados na fase de linkagem.
Tipos de carregadores Java
Existem três carregadores padrão em Java, cada um carregando uma classe de um local específico:-
Bootstrap é um carregador básico, também chamado Primordial ClassLoader.
carrega classes JDK padrão do arquivo rt.jar
-
Extensão ClassLoader – carregador de extensão.
carrega classes de extensão, que estão localizadas no diretório jre/lib/ext por padrão, mas podem ser definidas pela propriedade do sistema java.ext.dirs
-
System ClassLoader – carregador do sistema.
carrega classes de aplicativos definidas na variável de ambiente CLASSPATH
Classe abstrata ClassLoader
Cada carregador, com exceção do base, é descendente da classe abstratajava.lang.ClassLoader
. Por exemplo, a implementação do carregador de extensão é a classe sun.misc.Launcher$ExtClassLoader
e o carregador do sistema é sun.misc.Launcher$AppClassLoader
. O carregador base é nativo e sua implementação está incluída na JVM. Qualquer classe que se estenda java.lang.ClassLoader
pode fornecer sua própria maneira de carregar aulas com blackjack e essas mesmas. Para isso, é necessário redefinir os métodos correspondentes, que neste momento só posso considerar superficialmente, porque Não entendi esse problema em detalhes. Aqui estão eles:
package java.lang;
public abstract class ClassLoader {
public Class<?> loadClass(String name);
protected Class<?> loadClass(String name, boolean resolve);
protected final Class<?> findLoadedClass(String name);
public final ClassLoader getParent();
protected Class<?> findClass(String name);
protected final void resolveClass(Class<?> c);
}
loadClass(String name)
um dos poucos métodos públicos, que é o ponto de entrada para carregar classes. Sua implementação se resume a chamar outro método protegido loadClass(String name, boolean resolve)
, que precisa ser sobrescrito. Se você olhar o Javadoc deste método protegido, poderá entender algo como o seguinte: dois parâmetros são fornecidos como entrada. Um é o nome binário da classe (ou nome de classe totalmente qualificado) que precisa ser carregado. O nome da classe é especificado com uma lista de todos os pacotes. O segundo parâmetro é um sinalizador que determina se a resolução do link simbólico é necessária. Por padrão é false , o que significa que o carregamento lento da classe é usado. Além disso, de acordo com a documentação, na implementação padrão do método, é feita uma chamada findLoadedClass(String name)
que verifica se a classe já foi carregada anteriormente e, em caso afirmativo, retorna uma referência a esta classe. Caso contrário, o método de carregamento de classe do carregador pai será chamado. Se nenhum dos carregadores conseguir encontrar uma classe carregada, cada um deles, seguindo na ordem inversa, tentará encontrar e carregar essa classe, substituindo o arquivo findClass(String name)
. Isso será discutido com mais detalhes no capítulo “Esquema de carregamento de classes”. E por último, mas não menos importante, após o carregamento da classe, dependendo do sinalizador de resolução , será decidido se deseja carregar as classes através de links simbólicos. Um exemplo claro é que o estágio Resolução pode ser chamado durante o estágio de carregamento da classe. Da mesma forma, ao estender a classe ClassLoader
e substituir seus métodos, o carregador personalizado pode implementar sua própria lógica para entregar bytecode à máquina virtual. Java também suporta o conceito de carregador de classes "atual". O carregador atual é aquele que carregou a classe em execução no momento. Cada classe sabe com qual carregador foi carregada e você pode obter essa informação chamando seu arquivo String.class.getClassLoader()
. Para todas as classes de aplicação, o carregador "atual" geralmente é o do sistema.
Três princípios de carregamento de classe
-
Delegação
A solicitação para carregar a classe é passada para o carregador pai, e uma tentativa de carregar a classe em si será feita somente se o carregador pai não conseguir localizar e carregar a classe. Essa abordagem permite carregar classes com o carregador o mais próximo possível do carregador base. Isso alcança visibilidade máxima da classe. Cada carregador mantém um registro das classes que foram carregadas por ele, colocando-as em seu cache. O conjunto dessas classes é chamado de escopo.
-
Visibilidade
O carregador vê apenas “suas” classes e as classes do “pai” e não tem ideia das classes que foram carregadas por seu “filho”.
-
Singularidade
Uma classe só pode ser carregada uma vez. O mecanismo de delegação garante que o carregador que inicia o carregamento da classe não sobrecarregue uma classe que foi previamente carregada na JVM.
Esquema de carregamento de classe
Quando ocorre uma chamada para carregar uma classe, esta classe é pesquisada no cache de classes já carregadas do carregador atual. Se a classe desejada não tiver sido carregada antes, o princípio da delegação transfere o controle para o carregador pai, que está localizado um nível acima na hierarquia. O carregador pai também tenta encontrar a classe desejada em seu cache. Se a classe já tiver sido carregada e o carregador souber sua localização, um objetoClass
dessa classe será retornado. Caso contrário, a pesquisa continuará até chegar ao bootloader base. Se o carregador base não tiver informações sobre a classe necessária (ou seja, ainda não foi carregada), o bytecode desta classe será procurado no local das classes que o carregador determinado conhece, e se a classe não puder ser carregado, o controle retornará ao carregador filho, que tentará carregar a partir de fontes conhecidas por ele. Como mencionado acima, a localização das classes para o carregador base é a biblioteca rt.jar, para o carregador de extensão - o diretório com extensões jre/lib/ext, para o sistema - CLASSPATH, para o usuário pode ser algo diferente . Assim, o progresso do carregamento das classes segue na direção oposta - do carregador raiz para o atual. Quando o bytecode da classe é encontrado, a classe é carregada na JVM e uma instância do tipo é obtida Class
. Como você pode ver facilmente, o esquema de carregamento descrito é semelhante à implementação do método acima loadClass(String name)
. Abaixo você pode ver este diagrama no diagrama.
Como uma conclusão
Nas primeiras etapas do aprendizado de uma linguagem, não há necessidade específica de entender como as classes são carregadas em Java, mas conhecer esses princípios básicos o ajudará a evitar o desespero ao encontrar erros comoClassNotFoundException
ou NoClassDefFoundError
. Bem, ou pelo menos entender aproximadamente qual é a raiz do problema. Assim, ocorre uma exceção ClassNotFoundException
quando uma classe é carregada dinamicamente durante a execução do programa, quando os carregadores não conseguem encontrar a classe necessária no cache ou ao longo do caminho da classe. Mas o erro NoClassDefFoundError
é mais crítico e ocorre quando a classe necessária estava disponível durante a compilação, mas não estava visível durante a execução do programa. Isso pode acontecer se o programa esquecer de incluir a biblioteca que utiliza. Pois bem, o próprio facto de compreender os princípios da estrutura da ferramenta que utiliza no seu trabalho (não necessariamente uma imersão clara e detalhada nas suas profundezas) acrescenta alguma clareza à compreensão dos processos que ocorrem dentro deste mecanismo, que, em por sua vez, leva ao uso confiante desta ferramenta.
GO TO FULL VERSION