Tem que entender o mecanismo de carregamento de classe Java

Mecanismo de carregamento de classe Java

Processo de carregamento de classe

Existem três etapas principais para o sistema carregar arquivos do tipo Class: carregamento -> conexão -> inicialização . O processo de conexão pode ser dividido em três etapas: verificação -> preparação -> análise .

Insira a descrição da imagem aqui

carga

A primeira etapa do processo de carregamento da classe é concluir as três coisas a seguir:

  1. Obtenha o fluxo de bytes binário que define esta classe pelo nome completo da classe
  2. Converta a estrutura de armazenamento estático representada pelo fluxo de bytes na estrutura de dados de tempo de execução da área do método
  3. Gere um objeto Class representando a classe na memória como a entrada de acesso para esses dados na área do método

Parte do conteúdo da fase de carregamento e a fase de ligação são realizadas alternadamente, a fase de carregamento ainda não terminou, podendo a fase de ligação já ter começado.

verificação

  • Verificação do formato do arquivo: verifique principalmente se o arquivo de classe é padrão.
  • Verificação de metadados: análise semântica da informação descrita por bytecode, etc.
  • Verificação de bytecode: certifique-se de que a semântica esteja ok.
  • Verificação da referência do símbolo: para garantir que a ação de resolução possa ser executada.

pronto

A fase de preparação é a fase de alocar formalmente a memória para a variável de classe e definir o valor inicial da variável de classe, essas memórias serão alocadas na área de métodos. Nesta fase, os seguintes pontos devem ser observados:

  1. Neste momento, a alocação de memória inclui apenas variáveis ​​de classe (estáticas), não variáveis ​​de instância. As variáveis ​​de instância serão alocadas no heap Java junto com o objeto quando o objeto for instanciado.
  2. O valor inicial definido aqui "geralmente" é o valor zero padrão do tipo de dados (como 0, 0L, nulo, falso, etc.). Por exemplo public static int value=111, se definirmos , então o valor inicial da variável de valor no estágio de preparação é 0 em vez de 111 ( Ele será copiado durante a fase de inicialização). Caso especial: por exemplo public static final int value=111, se a palavra-chave fianl for adicionada à variável de valor , o valor de valor no estágio de preparação será copiado para 111.

Análise

A fase de análise é um processo no qual a máquina virtual substitui as referências de símbolo no pool constante por referências diretas. A ação de análise é realizada principalmente nos 7 tipos de referências de símbolo de classes ou interfaces, campos, métodos de classe, métodos de interface, tipos de método, identificadores de método e qualificadores de chamada.

A referência de símbolo é um conjunto de símbolos para descrever o alvo, que pode ser qualquer literal. Uma referência direta é um ponteiro que aponta diretamente para o destino, um deslocamento relativo ou uma alça que está localizada indiretamente para o destino. Quando o programa está realmente em execução, apenas referências de símbolo não são suficientes.Por exemplo, quando o programa executa um método, o sistema precisa saber onde o método está localizado. A máquina virtual Java prepara uma tabela de métodos para cada classe para armazenar todos os métodos da classe. Quando você precisa chamar um método de uma classe, você pode chamar o método diretamente, desde que você saiba o deslocamento do método na publicação do partido. Ao analisar a referência do símbolo de operação, ela pode ser transformada diretamente na posição do método de destino na tabela de métodos da classe, para que o método possa ser chamado.

Em suma, a fase de análise é o processo de substituição de referências de símbolos no pool constante por referências diretas da máquina virtual, ou seja, obtenção de ponteiros ou deslocamentos de classes ou campos e métodos na memória.

inicialização

A etapa final é inicializar o carregamento da classe, também executa o código Java real (bytecode) da classe definida na fase de inicialização é realizada <clinit> ()abordagem do processo do construtor da classe .

Para a <clinit> ()chamada do método, as próprias máquinas virtuais garantem sua segurança em um ambiente multithread. Como o <clinit>()método é seguro para thread de bloqueio, portanto, é inicialização de classe em um ambiente multithread, então pode causar um deadlock, e esse deadlock é difícil de encontrar.

Para a fase de inicialização, a máquina virtual é estritamente regulamentada. Existem apenas 5 casos em que a classe deve ser inicializada:

  1. Ao encontrar quatro instruções de código direto de new, getstatic, putstatic ou invokestatic, como uma nova classe, ler um campo estático (não modificado final) ou chamar um método estático de uma classe.
  2. Use java.lang.reflectquando o método do pacote chamar a classe para refletir, caso contrário, a inicialização da classe é necessária para acionar sua inicialização.
  3. Inicialize uma classe, se sua classe pai ainda não foi inicializada, a inicialização da classe pai é disparada primeiro.
  4. Quando a máquina virtual é iniciada, o usuário precisa definir uma classe principal a ser executada (a classe que contém o método principal), e a máquina virtual inicializa essa classe primeiro.
  5. Ao usar a linguagem dinâmica de JDK1.7, se a estrutura de análise final de uma instância de MethodHandle for o identificador de método de REF_getStatic, REF_putStatic, REF_invokeStatic e o identificador não for inicializado, você precisa acionar a inicialização primeiro.

Carregador de classes

Três importantes ClassLoaders são integrados à JVM. Todos os carregadores de classes, exceto BootstrapClassLoader, são implementados por Java e herdados deles java.lang.ClassLoader:

  1. BootstrapClassLoader (carregador de classe de boot) : carregamento de classe de nível superior, realizado por C ++, é responsável por carregar %JAVA_HOME%/libpacote jar e classe ou diretório, ou -Xbootclasspathtodos os parâmetros especificados no caminho de classe.
  2. ExtensionClassLoader (carregador de classe de extensão) : responsável principalmente por carregar o %JRE_HOME%/lib/extpacote jar do diretório e o diretório de classe, ou java.ext.dirspacote jar no caminho especificado das variáveis ​​do sistema.
  3. AppClassLoader (carregador de classes do aplicativo) : Um carregador para nossos usuários, responsável por carregar todos os pacotes jar e classes no classpath do aplicativo atual.

Depois de entender o processo de carregamento de classe e o carregador de classe, o que temos que entender é a forma de carregamento de classe: o mecanismo de delegação pai

Mecanismo de delegação parental

O processo do mecanismo de delegação pai é o seguinte:

Insira a descrição da imagem aqui

(1) O carregador de classes atual consulta se a classe foi carregada das classes que carregou e, se já tiver sido carregada, retornará diretamente à classe carregada originalmente.

(2) Se não for encontrado, ele confiará ao carregador de classe pai para carregá-lo (conforme mostrado no código c = parent.loadClass (name, false)). O carregador de classe pai também usará a mesma estratégia para verificar se a classe que ele já carregou contém essa classe. Se for, ele retornará. Caso contrário, ele confiará a classe pai da classe pai para carregá-la até que o carregador de classe seja iniciado. Porque se o carregador pai estiver vazio, isso significa usar o carregador de classes de inicialização como o carregador pai para carregar.

(3) Se o carregador de classe de inicialização falhar ao carregar (por exemplo, a classe não foi encontrada em $ JAVA_HOME / jre / lib), uma exceção ClassNotFoundException será lançada e, em seguida, o método findClass () do carregador atual será chamado para carregar.

Vantagens do modelo de delegação pai:

(1) Principalmente por segurança, para evitar a substituição dinâmica de algumas classes principais do Java, como String, por classes escritas por usuários.

(2) Ao mesmo tempo, o carregamento repetido de classes é evitado, porque a JVM distingue classes diferentes, não apenas com base no nome da classe, mas o mesmo arquivo de classe é carregado por ClassLoader diferente em duas classes diferentes.

Insira a descrição da imagem aqui

Carregador de classe personalizado

Etapa personalizada

1. Escreva uma classe que herde a classe abstrata ClassLoader

2. Substitua seu findClass()método

3. findClass()Chame o método defineClass().

ps: aqui defineClass()está o bytecode convertido em classe

ClassLoader personalizado

Primeiro, primeiro escrevemos um arquivo de classe para teste,

package ReWriteClassLoaderTest.tt;

public class Apple {
    
    
    public String name;
}

Em seguida, compilar esta classe em um arquivo de classe e colocá-lo em um lugar muito, muito distante, eu coloco aqui no diretório raiz da unidade f

Em seguida, construímos um ClassLoader personalizado

package ReWriteClassLoaderTest.tt;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.URL;

/**
 * <h3>firstIdeaProject</h3>
 * <p></p>
 *
 * @author : Nicer_feng
 * @date : 2020-09-10 14:57
 **/
public class MyClassLoader extends ClassLoader{
    
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        File file = new File("F:/Apple.class");
        //这里放哪了 就写哪里的路径
        try {
    
    
            FileInputStream fileInputStream = new FileInputStream(file);
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            //每次读取的大小是4kb
            byte[] b = new byte[4 * 1024];
            int n = 0;
            while ((n = fileInputStream.read(b)) != -1) {
    
    
                outputStream.write(b, 0, n);
            }
            //将Apple.class类读取到byte数组里
            byte[] classByteArray = outputStream.toByteArray();
            //调用defineClass 将byte 加载成class
            Class<?> aClass = this.defineClass(name, classByteArray, 0, classByteArray.length);
            return aClass;
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return super.findClass(name);
    }
}

Aqui, personalizamos o ClassLoader (), deixamos que ele herde o ClassLoader e, em seguida, reescrevemos o método findClass (), para que a lógica reescrita seja nosso próprio diretório especificado, ou seja, encontre o arquivo Apple.class na unidade f e o leia Em uma matriz de bytes e chame defineClass () para carregar esta classe.

Finalmente, uma simples classe de teste Demo

public class Demo {
    
    
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> aClass = Class.forName("ReWriteClassLoaderTest.tt.Apple",
                true, myClassLoader);
        Object o = aClass.newInstance();
        System.out.println(o);
        System.out.println(o.getClass().getClassLoader());
    }
}

Você pode ver que existem três parâmetros no método Class.forName, os dois últimos significam verdadeiros para recarregamento de classe e myClassLoader é nosso carregador de classe personalizado

Insira a descrição da imagem aqui

Exibir depois de correr

Insira a descrição da imagem aqui

Isso não atingiu o resultado que queríamos, ainda deixamos o AppClassLoader, porque nosso compilador é muito inteligente, compilar diretamente meu arquivo java, após a compilação, ele é colocado sob o classpath, temos que ir para o diretório de destino (ou saída) Encontre o arquivo correspondente e exclua-o, execute novamente, como segue

Insira a descrição da imagem aqui

Se você quiser um entendimento mais profundo, você pode ir

https://blog.csdn.net/briblue/article/details/54973413

problema

1. Fale sobre o mecanismo de delegação parental, por que usá-lo?

Processo de modelo de delegação pai: quando um carregador de classe particular recebe uma solicitação para carregar uma classe, ele primeiro delega a tarefa de carregamento para o carregador de classe pai e, em seguida, recursivamente. Se o carregador de classe pai pode concluir a tarefa de carregamento de classe, ele retorna com sucesso; apenas Quando o carregador de classes pai não consegue concluir esta tarefa de carregamento, ele carrega a si mesmo.

Vantagens do modelo de delegação pai:

(1) Principalmente por segurança, para evitar a substituição dinâmica de algumas classes principais do Java, como String, por classes escritas por usuários.

(2) Ao mesmo tempo, o carregamento repetido de classes é evitado, porque a JVM distingue classes diferentes, não apenas com base no nome da classe, mas o mesmo arquivo de classe é carregado por ClassLoader diferente em duas classes diferentes.

2. Por que você deseja personalizar o carregador de classes?

A razão simples é porque o carregador de classes fornecido pelo sistema não pode atender à aplicação real de determinados cenários.

Semelhante a um servidor javaWeb, vários sites podem ser implantados em um servidor, e cada site usará algumas das mesmas bibliotecas de classes. Se apenas o carregador de classes do sistema for usado, apenas uma biblioteca de classes pode existir. Para diferentes versões de bibliotecas de classes Não pode existir ao mesmo tempo; suporte JSP hot substituto, JSP eventualmente será compilado em um arquivo .class para ser executado pela máquina virtual, mas a probabilidade de modificação durante o tempo de execução JSP é muito maior do que a de bibliotecas de classes de terceiros ou seus próprios arquivos .class e aplicativos da web como JSP A necessidade de reiniciar após a modificação também é considerada uma grande vantagem Os carregadores fornecidos por esses sistemas não podem ser totalmente satisfeitos e você mesmo precisa definir os carregadores.

Existem várias respostas comuns online:

(1) Criptografia: o código Java pode ser facilmente descompilado . Se você precisar criptografar seu próprio código para evitar a descompilação, primeiro criptografe o código compilado com um determinado algoritmo de criptografia. Depois que a classe for criptografada, não será mais possível usar Java. O ClassLoader para carregar a classe, então você precisa personalizar o ClassLoader para descriptografar a classe ao carregar a classe e, em seguida, carregá-la.

(2) Carregar código de fontes não padrão: Se seu bytecode for colocado em um banco de dados ou mesmo na nuvem, você pode personalizar o carregador de classes para carregar classes de fontes especificadas.

(3) Aplicação abrangente das duas situações acima na prática: Por exemplo, seu aplicativo precisa transmitir bytecodes Java através da rede.Para segurança, esses bytecodes são criptografados. Neste momento, você precisa customizar o carregador de classes para ler o código de byte criptografado de um determinado endereço de rede, descriptografar e verificar e, finalmente, definir a classe que é executada na máquina virtual Java.

3. O processo de carregamento de classes de classes Java comuns é o mesmo do Tomcat? Qual é a diferença?

Vejo

https://blog.csdn.net/dreamcatcher1314/article/details/78271251

4. O que é um carregador de classes

O carregador de classe deve carregar o arquivo de classe na máquina virtual, ou seja, obter o fluxo de bytes binário que descreve a classe por meio do nome totalmente qualificado da classe.

O carregador de classes é uma inovação da linguagem Java, originalmente projetada para atender às necessidades do Java Applet. Os carregadores de classes são usados ​​atualmente com frequência em campos como divisão hierárquica, implementação de programas ativos e criptografia de código.

5. Quais são os tipos de carregadores de classes?

A JVM nos fornece um carregador de classes do sistema (JDK1.8) por padrão, incluindo:

Bootstrap ClassLoader (carregador de classes do sistema)

Extension ClassLoader

Application ClassLoader (carregador de classes do aplicativo)

Customer ClassLoader (carregador personalizado)

Acho que você gosta

Origin blog.csdn.net/weixin_43876186/article/details/108514678
Recomendado
Clasificación