Exploración en profundidad de ClassLoader en la máquina virtual Java (parte externa)

Este artículo se 深入Java虚拟机(四)basa en algunas ideas, desde otro aspecto, para estudiar el problema de la carga de clases.

Programación orientada a interfaces

Una interfaz para obtener el saldo de la billetera se define en el catálogo de ingeniería del proyecto, y como una persona común, regresará a 50 océanos para que usted coma, beba y juegue.

public interface Pocket {
    
    
    int getBalance();
}
public class NormalUser implements Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50;
    }
}

Compilé otra versión en el escritorio NormalUsery agregué una cuota a mi billetera.

public class NormalUser implements Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50000;
    }
}

Ahora quiero mejorar la calidad de vida, así que hice lo siguiente:

Personalice un cargador de clases en el proyecto:

public class LocalClassLoader extends ClassLoader {
    
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        String path = "/Users/lijie/Desktop/NormalUser.class";
        try {
    
    
            FileInputStream ins = new FileInputStream(path);
            int size = ins.available();
            byte[] clazzBytes = new byte[size];
            if (ins.read(clazzBytes) > 0) {
    
    
                return defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
            } else {
    
    
                throw new ClassNotFoundException();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        throw new ClassNotFoundException();
    }
}

LocalClassLoaderLa escritura es relativamente rígida, pero la idea de ganar dinero está ahí. . . .

LocalClassLoaderReescribir el findClassmétodo no se anula loadClass, por lo que 双亲委托机制también es efectivo. Para los tipos que no se pueden encontrar en el findClasssistema, vendré aquí, y en el método, NormalUsercargaré el escritorio .

Veamos el código de prueba:

public class ClassLoaderTest {
    
    
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    
        Class<?> clazz = Class.forName("hua.lee.classloader.NormalUser");
        System.out.println("系统自带 ClassLoader=" + clazz.getClassLoader());
        Pocket pocket = (Pocket) clazz.newInstance();
        System.out.println("系统自带:"+pocket.getBalance());

        //没有重写 loadClass,维持双亲委派机制
        LocalClassLoader lcl = new LocalClassLoader();
        //我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
        //其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
        //defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
        Class<?> clazzOut02 = lcl.loadClass("hua.lee.classloader.NormalUser_Temp");

        //双亲委托机制先检查当前类加载器中有没有这个类的 Class 实例,没有的话再搜寻父类,
        //查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
        Class<?> clazzOut01 = lcl.loadClass("hua.lee.classloader.NormalUser");


        System.out.println("clazzOut01 自定义 ClassLoader=" + clazzOut01.getClassLoader());
        pocket = (Pocket) clazzOut01.newInstance();
        System.out.println("clazzOut01 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println("clazzOut02 自定义 ClassLoader=" + clazzOut02.getClassLoader());
        pocket = (Pocket) clazzOut02.newInstance();
        System.out.println("clazzOut02 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println(clazz==clazzOut01);
        System.out.println(clazz==clazzOut02);
        System.out.println(clazzOut01==clazzOut02);
    }

La salida de la consola es así:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut01 自定义加载器获取余额:50000
clazzOut02 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut02 自定义加载器获取余额:50000
false   //clazz==clazzOut01
false   //clazz==clazzOut02
true    //clazzOut01==clazzOut02

Siento que he encontrado la manera de hacerme rico. No 加载约束lo vi en la máquina virtual .

Programación abstracta

El camino anterior hacia la riqueza parece ser muy sencillo, así que intentemos de la siguiente manera:

public abstract class Pocket {
    
    
    abstract int getBalance();
}
public class NormalUser extends Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50;
    }
}

Del mismo modo, compilé una versión enriquecida en el escritorio:

public class NormalUser extends Pocket{
    
    
    @Override
    public int getBalance() {
    
    
        return 50000;
    }
}

LocalClassLoaderY el código de prueba no ha cambiado, pero el resultado impreso es así:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" java.lang.AbstractMethodError
	at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.java:25)

¿Existe alguna diferencia entre el análisis de la máquina virtual 抽象类实现y 接口实现el tiempo ?

Especulación inmadura

El primer 面向接口modo debería evitar las restricciones de carga, o la máquina virtual está diseñada para admitir interfaces como esta.

Ahora que se le solicita AbstractMethodError, cambiaré el código de prueba, ya no usaré clases abstractas Pocket, apuntaré directamente a NormalUser:

public class ClassLoaderTest {
    
    
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    
    

        Class<?> clazz = Class.forName("hua.lee.classloader.NormalUser");
        System.out.println("系统自带 ClassLoader=" + clazz.getClassLoader());
                    //请注意此处⬇️
        NormalUser pocket = (NormalUser) clazz.newInstance();
                    //请注意此处⬆️
        System.out.println("系统自带:"+pocket.getBalance());

        //没有重写 loadClass,维持双亲委派机制
        LocalClassLoader lcl = new LocalClassLoader();
        //我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
        //其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
        //defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
        Class<?> clazzOut02 = lcl.loadClass("hua.lee.classloader.NormalUser_Temp");

        //双亲委托机制会自动搜寻父类,
        //查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
        Class<?> clazzOut01 = lcl.loadClass("hua.lee.classloader.NormalUser");


        System.out.println("clazzOut01 自定义 ClassLoader=" + clazzOut01.getClassLoader());
        pocket = (NormalUser) clazzOut01.newInstance();
        System.out.println("clazzOut01 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println("clazzOut02 自定义 ClassLoader=" + clazzOut02.getClassLoader());
        pocket = (NormalUser) clazzOut02.newInstance();
        System.out.println("clazzOut02 自定义加载器获取余额:"+pocket.getBalance());

        System.out.println(clazz==clazzOut01);
        System.out.println(clazz==clazzOut02);
        System.out.println(clazzOut01==clazzOut02);
    }
}

Llegó la sorpresa y el resultado fue el siguiente:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" java.lang.ClassCastException: hua.lee.classloader.NormalUser cannot be cast to hua.lee.classloader.NormalUser
	at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.java:24)

Esta anomalía indica que un problema que cargamos de manera personalizada hua.lee.classloader.NormalUserno es lo que quiere la máquina virtual hua.lee.classloader.NormalUser.

La primera pregunta: ¿Cómo NormalUsersabe la máquina virtual que estos dos son diferentes?

Tiene 定义类装载器algo que ver con, si el tipo con el mismo nombre completo no es el mismo 定义类装载器, la máquina virtual lo considerará como no del mismo tipo. (De acuerdo con las restricciones de carga, se debe informar un error)

¿Cuál es la definición de cargador de clases? Es el ClassLoader el que realmente carga el archivo de clase

La segunda pregunta: ¿Por qué la interfaz puede funcionar así?

Eso es porque 面向接口实现la versión no nombra explícitamente el tipo NormalUser. Las conversiones de tipos se Pocketrealizan según el tipo. La máquina virtual encuentra automáticamente la implementación del tipo de interfaz. Siempre que se Pocketrealice.

Si lo cambia pocket = (Pocket) clazzOut01.newInstance();a pocket = (NormalUser) clazzOut01.newInstance();, 面向接口la versión también se informará en este momento ClassCastException.

Para la máquina virtual, en la clase de implementación de la interfaz, no le importa la fuente de su clase de implementación (no importa qué cargador la cargue), solo se ajusta a la definición de la interfaz.

Pero cuando especifica la conversión de tipos, tiene que realizar una verificación de tipos.

La tercera pregunta: ¿Por qué no funcionan las clases abstractas?

Primer AA java.lang.AbstractMethodError:

Cuando Pocketes una clase abstracta, de acuerdo con la definición de la clase abstracta, debe haber una clase de implementación específica para ejecutar.
Para pocket = (Pocket) clazzOut01.newInstance();esta oración, Pocketla conversión de tipo forzada es exitosa (porque el Pokettipo de análisis necesita ejecutar la 双亲委派lógica, después del análisis, se encuentra que todos son iguales Poket), pero al buscar la clase de implementación, encontré que el clazzOut01.newInstance()tipo correspondiente de este objeto NormalUserno es el que quiero, virtual La máquina piensa que no hay una implementación correspondiente, por lo que cuando llega la ejecución pocket.getBalance(), la máquina virtual piensa que el programa debería ser Pocketun método de ejecución puramente abstracto y reporta un error java.lang.AbstractMethodError.

Si se entiende lo anterior, entonces java.lang.ClassCastExceptiones fácil de entender:

Para pocket = (NormalUser) clazzOut01.newInstance();esta oración, la máquina virtual clazzOut01.newInstance()forzó la conversión del objeto a NormalUser, y aquí está la respuesta a la primera pregunta: clazzOut01la conversión representativa NormalUsery forzada NormalUserno es del mismo tipo, la máquina virtual reporta directamente un error

Conclusión

¿Por qué esto tiene un final?

Debido a 不成熟的推测que es realmente el resultado de mi propio YY, no puedo garantizar que sea verdadero y efectivo. Si hay errores, no dudes en aclararme.

El próximo artículo continuará Java虚拟机(五)垃圾收集(siempre siento que tengo que escribir sobre la clasificación de la basura, del tipo que los cerdos no pueden comer)

Supongo que te gusta

Origin blog.csdn.net/lijie2664989/article/details/107070141
Recomendado
Clasificación