Processo de carregamento de classe
O trabalho principal de um carregador de classe é carregar arquivos de classe na JVM. Como mostrado na figura abaixo, o processo é dividido em três etapas:
1. Carregando: localize o arquivo de classe a ser carregado e carregue seu fluxo de bytes na JVM;
2. Link: aloce a estrutura de memória mais básica para carregar para salvar suas informações, como propriedades, métodos e classes referenciadas. Nesta fase, a classe ainda não está disponível;
(1) Verificação: Verifique o fluxo de bytes carregado, como formato e segurança;
(2) alocação de memória: prepare o espaço de memória para esta classe representar suas propriedades, métodos e classes referenciadas;
(3) Análise: Carregue outras classes referenciadas por esta classe, como classe pai, interfaces implementadas, etc.
3. Inicialização: atribua valores às variáveis de classe.
O nível do carregador de classe
A linha pontilhada na figura abaixo são vários carregadores de classe importantes fornecidos pelo JDK, e a descrição detalhada é a seguinte:
(1) carregador de classe Bootstrap: Ao iniciar uma classe que contém a função principal, carregue o pacote JAR no diretório java_home/lib ou -xbootclasspath especificado;
(2) Carregador da classe de extensão: carregue o pacote JAR no diretório java_home/lib/ext ou -djava.ext.dirs.
(3) Carregador da classe do sistema: carregar classe de classe ou -djava.class.path Classe ou pacote JAR no diretório especificado.
O que você precisa saber é:
1. Além do carregador de classe Bootstrap, outros carregadores de classe são subclasses da classe java.lang.classloader;
2. O carregador de classe Bootstrap não é implementado em Java. Se você não usar um carregador de classe personalizado, java.lang.string.class.getclassloader () será nulo e o carregador de pais do carregador de classe de extensão também será nulo;
3. Várias maneiras de obter carregadores de classe:
(1) Obtenção de carregador de classe de bootstrap: o que você obtém deve ser nulo ao tentar obter o carregador de classe de bootstrap. Você pode verificá -lo da seguinte maneira: use o método getClassLoader de objetos de classe no pacote rt.jar, como java.lang.string.class.getclassloader () para obter ou obter o carregador de classe de extensão e, em seguida, chamar o método getParent para obter;
(2) Obtenha o carregador de classe de extensão: use o método getClassLoader do objeto de classe no pacote JAR no diretório java_home/lib/ext ou primeiro obtenha o carregador de classe do sistema e depois o obtenha através de seu método getParent;
(3) Obtenha o carregador da classe do sistema: ligue para o método getClassLoader do objeto de classe que contém a função principal ou ligue para Thread.currentThread (). GetContextClassloader () ou Call Classloader.getSystemClassLoadeler () dentro da função principal;
(4) Obtenha o carregador de classe definido pelo usuário: ligue para o método getClassLoader do objeto de classe ou ligue para Thread.currentThread (). GetContextClassLoader ();
Princípios operacionais do carregador de classe
1. Princípio do agente
2. Princípio da visibilidade
3. O princípio da singularidade
4. Princípio do proxy O princípio do proxy refere -se a um carregador de classe que solicita que seu proxy do carregador pai seja carregado ao carregar uma classe, e o carregador pai também solicitará que seu proxy do carregador pai seja carregado, conforme mostrado na figura abaixo.
Por que usar o modo proxy? Primeiro de tudo, isso pode reduzir o carregamento duplicado de uma classe. (Existe algum outro motivo?)
Onde entender mal:
Geralmente, acredita -se que a ordem de proxy do carregador de classe seja pai primeiro, ou seja::
1. Ao carregar uma classe, o carregador de classe verifica primeiro se ele carregou a classe. Se tiver sido carregado, retornará; Caso contrário, peça ao carregador pai para proxy;
2. O carregador pai repete a operação de 1 até o carregador de classe de bootstrap;
3. Se o carregador de classe Bootstrap não carregar a classe, ele tentará carregá -lo e, se for bem -sucedido, ele retornará; Se falhar, uma classe NOTFoundException será lançada, será carregada pelo carregador infantil;
4. O carregador subclasse pega a exceção e tenta carregá -lo. Se for bem -sucedido, retorna. Se falhar, lança uma classe NOTFoundException até que o carregador de subclasse carregado seja iniciado.
Esse entendimento é correto para carregadores, como carregador de classe de bootstrap, carregador de classe de extensão e carregador de classe do sistema, mas alguns carregadores personalizados não são o caso. Por exemplo, alguns carregadores de classe implementados pelo IBM Web Sphere Portal Server são os pais, que são os filhos que são o carregador filho que tenta carregar primeiro. Se a carga falhar, o carregador pai será solicitado. A razão para isso é: se você espera que uma certa versão do log4j seja usada por todos os aplicativos, coloque -o na biblioteca WAS_HOME e será carregada quando foi iniciado. Se um aplicativo desejar usar outra versão do LOG4J, ele não poderá ser alcançado se usar o pai primeiro porque a classe no log4j já está carregada no carregador pai. No entanto, se o último pai for usado, o carregador de classe responsável pelo carregamento do aplicativo priorizará o carregamento de outra versão do log4j.
Princípio da visibilidade
A visibilidade de cada classe para o carregador de classe é diferente, como mostrado na figura abaixo.
Para estender o conhecimento, a OSGI aproveita esse recurso. Cada pacote é carregado por um carregador de classe separado, para que cada carregador de classe possa carregar uma versão de uma determinada classe, para que todo o sistema possa usar várias versões de uma classe.
O princípio da singularidade
Cada classe é carregada no máximo uma vez em um carregador.
Conhecimento estendido 1: Para ser preciso, o padrão Singleton refere -se a um objeto Singleton que apenas uma cópia de uma classe em um conjunto de carregadores de classe.
Conhecimento estendido 2: Uma classe pode ser carregada por vários carregadores de classe. Cada objeto de classe está em seu próprio espaço para nome. Ao comparar o objeto de classe ou tipo convertendo a instância, ele comparará seu respectivo espaço para nome ao mesmo tempo, como:
A classe Klass é carregada por Classloadra, assumindo que o objeto de classe seja Klassa; Ele é carregado pelo ClassLoaderb, assumindo que o objeto de classe é KlassB, então o Klassa não é igual a KlassB. Ao mesmo tempo, quando a instância da Classa é lançada para Klassb, uma exceção de ClassCastException será lançada.
Por que você precisa de um carregador de classe personalizado? O carregador de classe personalizado adiciona muita flexibilidade ao idioma Java. Os principais usos são:
1. As classes podem ser carregadas em vários lugares, como na rede, no banco de dados ou até mesmo arquivos de origem compilados instantaneamente;
2. Após a personalização, o carregador de classe pode carregar uma determinada versão do arquivo de classe em princípio em tempo de execução;
3. Após a personalização, o carregador de classe pode descarregar dinamicamente algumas classes;
4. Após a personalização, o carregador de classe pode descriptografar e descomprimir a classe antes de carregar a classe.
Carregamento implícito e explícito de classes
Carregamento implícito: quando uma classe é referenciada, herdada ou instanciada, ela será carregada implicitamente. Se a carga falhar, o noclassDeffoundError será jogado.
Carregamento explícito: use o método a seguir, se a carga falhar, uma ClassNotFoundException será lançada.
cl.loadclass (), cl é uma instância do carregador de classe;
Class.ForName (), carrega usando o carregador de classe da classe atual.
Se a execução do bloco estático da classe tiver as seguintes classes:
pacote cn.fengd; public class Dummy {static {System.out.println ("Hi"); }} Crie outra classe de teste:
pacote cn.fengd; classe pública classeLoadetestest {public static void main (string [] args) lança instantiationException, exceção {tente { / * * diferentes maneiras de carregar. */ Classe C = ClassLoadeRestest.class.getclassloader (). Loadclass ("cn.fengd.dummy"); } Catch (Exceção e) {// TODO BLOCO DE CAPAGEM AUTOMENTADO E.PRINTSTACKTRACE (); }}}Quão eficaz é depois de correr?
E se for substituído por classe.ForName ("cn.fengd.dummy"); ou novo manequim ()?
Oi será emitido.
Perguntas frequentes:
1. O tipo especificado é carregado por diferentes carregadores de classe ainda é o mesmo tipo?
Em Java, uma classe usa seu nome de classe totalmente qualificado como seu identificador. O nome exato da classe de correspondência referido aqui inclui o nome do pacote e o nome da classe. No entanto, na JVM, uma classe usa seu nome completo e uma instância do carregador de classe de carregamento como identificadores exclusivos. As classes carregadas por diferentes carregadores de classe serão colocadas em diferentes namespaces. Podemos usar dois carregadores de classe personalizados para carregar um tipo personalizado (observe que não coloque o bytecode do tipo personalizado no caminho do sistema ou no caminho de extensão; caso contrário, será carregado primeiro pelo carregador de classe do sistema ou pelo carregador de classe de extensão) e, em seguida, usar as duas instâncias de classe obtidas para fazer com que o java.lang.Object.Equals (…) Isso pode ser feito escrevendo dois carregadores de classe personalizados para carregar o mesmo tipo personalizado e, em seguida, fazer um julgamento; Ao mesmo tempo, você pode testar o carregamento de Java.* Tipo e comparar e testar os resultados do teste.
2. Ligue diretamente ao método Class.ForName (Nome da String) no código. Qual carregador de classe acionará o comportamento de carregamento da classe?
Class.ForName (Nome da String) usará o carregador de classe que chama a classe para carregar classe por padrão. Vamos analisar diretamente o código JDK correspondente:
//java.lang.class.java Classe publicstatic <?> forname (string className) lança ClassNotFoundException {return forName0 (className, true, classloader.getCalerClassloader ());} // java.lang.classloader.java// ReturnS) getCallerClassloader () {// Obtenha o tipo de classe de chamada (classe) Class CLOKLER = Reflection.GetCalerClass (3); // Isso pode ser nulo se a VM estiver solicitando se (callador == null) {returnNull; } // Ligue para o método local em java.lang.class para obter o carregador de classe que carrega a classe de chamada (chamador) retorna call.getclassloadler0 ();} // java.lang.class.java//native implementação da máquina virtual para obter o carregador de classe da classe Native ClassLoader GetClloader0 (); 3. Ao escrever um carregador de classe personalizado, se o carregador pai não estiver definido, qual é o carregador pai?
Quando o carregador da classe pai não é especificado, o carregador de classe do sistema é usado por padrão. Algumas pessoas podem não entender, agora vamos dar uma olhada na implementação de código correspondente do JDK. Como todos sabemos, escrevemos um carregador de classe personalizado direta ou indiretamente herdando da classe abstrata java.lang.classloader. O construtor padrão correspondente sem parâmetros é implementado da seguinte forma:
// Trecho de java.lang.classloader.javaprotected Classloader () {SecurityManager Security = System.getSecurityManager (); if (segurança! = null) {Security.CheckCreateClassLoader (); } this.parent = getSystemClassLoader (); inicializado = true;}Vamos dar uma olhada na implementação correspondente do método getSystemClassLoader ():
PrivateStaticsynchronizedVoid initsystemClassLoader () {// ... Sun.misc.launcher l = Sun.misc.launcher.getLauncher (); scl = l.getclassloader (); // ...}Podemos escrever um código de teste simples para testá -lo:
System.out.println (Sun.misc.launcher.getLauncher (). GetClassLoader ());
A saída correspondente desta máquina é a seguinte:
sun.misc.launcher$AppClassloadner@197D257
Portanto, agora podemos acreditar que, quando o carregador de classe personalizado não especificar o carregador da classe pai, o carregador de classe pai padrão é o carregador de classe do sistema. Ao mesmo tempo, podemos tirar as seguintes conclusões:
O carregador de classe instantâneo definido pelo usuário não especifica o carregador da classe pai, as classes nos três lugares a seguir também podem ser carregadas:
(1) Classes sob <Java_runtime_home>/lib
(2) Classes no local especificado pela variável do sistema java.ext.dir
(3) Classes sob o caminho atual da classe do projeto ou no local especificado pela variável do sistema java.class.path
4. Ao escrever um carregador de classe personalizado, o que acontecerá se o carregador da classe pai for forçado a ser nulo? Se o carregador de classe personalizado não puder carregar a classe especificada, ela definitivamente falhará?
A especificação da JVM estipula que, se o carregador de classe definido pelo usuário forçar o carregador da classe pai a nulo, o carregador de classe de inicialização será definido automaticamente como o carregador de classe pai do carregador de classe definido pelo usuário (esse problema foi analisado antes). Ao mesmo tempo, podemos tirar as seguintes conclusões:
Se o carregador de classe definido pelo usuário instantâneo não especificar o carregador da classe pai, ele também poderá carregar a classe em <Java_runtime_home>/lib, mas a classe em java_runtime_home>/lib/extlution não poderá ser carregada neste momento.
Nota: As conclusões inferidas das perguntas 3 e 4 são baseadas no próprio carregador de classe definido pelo usuário, que continua a lógica de delegação padrão de java.lang.classloader.loadclass (…). Se o usuário alterar essa lógica de delegação padrão, a conclusão inferida acima poderá não ser válida. Veja a pergunta 5 para obter detalhes.
5. Quais são os pontos gerais de atenção ao escrever carregadores de classe personalizados?
(1) Geralmente, tente não substituir a lógica de delegação no método de classe de carga existente (…). Geralmente, isso é feito em versões antes do JDK 1.2, e é muito provável que isso faça com que o carregador de classe padrão do sistema não funcione corretamente. Na especificação da JVM e na documentação do JDK (versões 1.2 ou posteriores), não é recomendável que os usuários substituam o método da classe de carga (…). Por outro lado, os desenvolvedores são claramente lembrados de substituir a lógica FindClass (...) ao desenvolver um carregador de classe personalizado. Dê um exemplo para verificar o problema:
// Classe personalizada do usuário carregador WHRINGCLASSLOVER.java (Logic de LouadClass sobreWrite) publicClassWrongClassLoaderextends ClassLoader {public class <? } classe protegida <?> findClass (nome da string) lança ClassNotFoundException {// Suponha aqui apenas para um diretório específico que não seja o projeto d: /biblioteca para carregar o código de implementação específico da classe omitido}}Através da análise anterior, já sabemos que o padrão do carregador de classe definido pelo usuário (WhryClassLoader)
O carregador de classe reconhecido é um carregador de classe do sistema, mas as conclusões dos quatro tipos de problemas não são válidas agora. Todos podem
Basta testar, agora <Java_runtime_home>/lib, <Java_runtime_home>/lib/ext e
As classes no caminho da classe do programa não podem ser carregadas.
Pergunta 5 Código de teste 1
publicClass WHADClassLoadestest {publicstaticVoid Main (String [] args) {try {WhritClassLoader carregador = new WhritClassLoader (); Classe classeLoaded = carregador.loadclass ("beans.account"); System.out.println (classLoaded.getName ()); System.out.println (classLoaded.getClassLoader ()); } catch (Exceção e) {e.printStackTrace (); }}}(Nota: D: "Classes" Beans "Account.Class Existe fisicamente)
Resultado da saída:
java.io.fileNotFoundException: D: "Classes" Java "Lang" Object.class (o sistema não pode encontrar o caminho especificado.) Em java.io.fileInputStream.open (método nativo) em java.io.fileinputStream. WrongClassLoader.findClass(WrongClassLoader.java:40) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.classloader.defineclass (classe de classe.java:620) em java.lang.classloader.defineclass (classe de classe.java:400) em hordclassload.findclass (hordclassloader.java:43) em hordclassloader.findClass (hordclassloader.java:43) em hordclassload. WHRINGCLASSINGRERTERTEST.MAIN (WHRINCLASSLOADERTEST.JAVA:27) Exceção no thread "Main" java.lang.noclassDeffoundError: java/lang/objeto em java.lang.classloadler.defineClass1 (método nativo) em java.lang.classloader.DefinLefinLas java.lang.classloader.defineclass (classe de classe.java:400) em hordclassloader.findclass (hordclassloadler.java:43) em hordclassloader.loadclass (hordclassloader.java:29) em heldclassotertest.MainJLloadSlowerTest (WhrylClassLoader.java:29) em heldClassic.
Isso significa que mesmo o supertipo java.lang.Object do tipo a ser carregado não pode ser carregado. Os erros lógicos listados aqui causados pela substituição da classe (…) são obviamente relativamente simples, e os erros lógicos causados na verdade podem ser muito mais complicados.
Pergunta 5 Teste 2
// Usuário CLASS CLOARDER CULTER WINDCLASSLOADER.java (não substitua a lógica do loadclass) publicClassWrongClassLoaderextends ClassLoader {classe protegida <?> FindClass (nome da string) lança classeNotFoundException {// suponha aqui que seja apenas um diretório específico que não seja o projeto: /Library para carregarDepois de modificar o código do carregador de classe personalizado WHADClassLoader.java, execute o código de teste e o resultado da saída é o seguinte:
beans.accountwrongclassloader@1c78e57
Isso mostra que o Beans.Account é carregado com êxito e é carregado pelo carregador de classe personalizado WHADClassLoader.
Eu não acho que há necessidade de explicar as razões para isso, e você poderá analisá -lo.
(2) Configure corretamente o carregador da classe pai através da análise da pergunta 4 e da pergunta 5 acima. Pessoalmente, acho que esse é o ponto mais importante ao personalizar carregadores de classe de usuário, mas geralmente é ignorado ou facilmente trazido. Com a análise do código JDK anterior como base, acho que todos podem dar qualquer exemplo agora.
(3) Verifique se a correção lógica do método FindClass (String), tente entender com precisão as tarefas de carregamento a serem concluídas pelo carregador de classe a ser definido e verifique se o conteúdo do ByteCode correspondente pode ser obtido na maior extensão.
6. Como determinar quais caminhos o carregador de classe do sistema pode carregar?
Primeiro, você pode chamar diretamente o ClassLoader.GetSystemClassLoader () ou outros métodos para obter o carregador de classe do sistema (o carregador de classe do sistema e o carregador de classe de extensão são derivados do URLClassLoader) e você pode obtê -lo chamando o método getUrls () no URLClassLoader;
Segundo, você pode visualizar diretamente as informações de entrada no caminho de classe atual, obtendo o atributo do sistema java.class.path. System.getProperty ("java.class.path")
7. Como determinar quais caminhos o carregador de classe de extensão padrão pode carregar?
Método um:
tente {url [] exturls = (((urlclassloader) classloader.getSystemClassLoader (). getParent ()). geturls (); for (int i = 0; i <exturls.length; i ++) {System.out.println (exturls [i]); }} catch (Exceção e) {//…}A saída correspondente desta máquina é a seguinte:
Arquivo:/d: /demo/jdk1.5.0_09/jre/lib/ext/dnsns.jarfile:/d: /demo/jdk1.5.0_09/jre/lib/ext/localedata.jarfile :/D: /demo/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jarfile:/d: /demo/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar