Análisis en profundidad del cargador de clases y análisis de características importantes (3)

1. Cargador de clases

El cargador de clases se usa para cargar clases en la máquina virtual Java. A partir de JDK 1.2, el proceso de carga de clases utiliza un mecanismo de delegación dual, que puede garantizar mejor la seguridad de la plataforma Java. En este mecanismo de delegación, además del cargador de clases raíz que viene con la máquina virtual Java, el resto de los cargadores de clases tienen solo un cargador principal. Cuando el programa Java solicita a Loader1 que cargue la muestra de clase, primero le confía al cargador de clases padre que lo cargue, y devuelve si se puede cargar; de lo contrario, el cargador Loader1 carga la muestra.


2. Tipos de cargadores de clases

  • El cargador de clases que viene con la máquina virtual Java
  • Root class loader (BootStrap):
    este cargador de clases no tiene un cargador de clases padre y es implementado por una máquina virtual. Es el principal responsable de cargar la biblioteca de clases principales de la máquina virtual, como: java.lang.*etc. El cargador raíz sun.boot.class.pathcarga la biblioteca de clases desde el directorio especificado por las propiedades del sistema . La implementación subyacente del cargador raíz depende principalmente del sistema operativo subyacente y es parte de la implementación de la máquina virtual. No hereda la clase java.lang.ClassLoader.

  • Extension class loader (Extension):
    el cargador principal de este cargador de clases es el cargador raíz. Es los java.ext.dirsdirectorio especificado bibliotecas atributo del sistema de carga, o se instala desde un directorio JDK jre/lib/extsubdirectorio (directorio extendido) para cargar la biblioteca, creado por el usuario si el frasco, colocado en el directorio, carga automáticamente por la clase de extensión器 载。 Cargador. El cargador de clases de extensión es una clase pura de Java y es una subclase de java.lang.ClassLoader

  • Cargador de clases del sistema (aplicación) (Sistema):
    el cargador principal de este cargador de clases es un cargador de clases de extensión. Carga clases desde la variable de entorno classpath o el directorio especificado por la propiedad del sistema java.class.path. Es el cargador primario predeterminado del cargador de clases definido por el usuario. El cargador de clases de la aplicación es una clase Java pura, que es java.lang.ClassLoader Subclase

  • Cargador de clases definido por el usuario
  • java.lang.ClassLoaderSubclase
  • Los usuarios pueden personalizar el método de carga de la clase necesita heredarjava.lang.ClassLoader

Punto de extensión:

  • La especificación JVM permite que un cargador de clases cargue previamente una clase cuando se espera que se use.Si .classfalta un archivo o hay un error durante el proceso de carga previa , el cargador de clases debe 程序首次主动使用informar un error (LinkageError) cuando la clase está en uso.
  • Si esta clase no ha sido utilizada activamente por el programa, entonces el cargador de clases no informará un error.

El diagrama de flujo de carga del cargador de clases:
Inserte la descripción de la imagen aquí


3. Ejemplo de código de cargador de clase personalizado

Código:

public class ClassLoaderTest extends ClassLoader {

  private String classLoaderName;

  private static final String FILE_EXTENSION = ".class";


  public ClassLoaderTest(String classLoaderName) {
      // 使用父类默认方式,将应用类加载器设置为该类加载器的父加载器
      super();
      this.classLoaderName = classLoaderName;
  }

  public ClassLoaderTest(ClassLoader parentClassLoader, String classLoaderName) {
      // 显示指定该类的父加载器
      super(parentClassLoader);
      this.classLoaderName = classLoaderName;
  }


  @Override
  protected Class<?> findClass(String className) throws ClassNotFoundException {
      System.out.println("find class execute!");
      byte[] data = loadClassData(className);
      return this.defineClass(className, data, 0, data.length);
  }

  private byte[] loadClassData(String name) {
      try (InputStream inputStream = new FileInputStream(new File(name + this.FILE_EXTENSION));
          ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
          int ch = 0;
          while (-1 != (ch = inputStream.read())) {
              baos.write(ch);
          }
          return baos.toByteArray();
      } catch (Exception e) {
          e.getStackTrace();
      }
      return null;
  }

  public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
      ClassLoaderTest classLoaderTest = new ClassLoaderTest("classLoaderTest");
      Class<?> clazz = classLoaderTest.loadClass("com.jvm.test.MyTest1");
      System.out.println(clazz);
      Object o = clazz.newInstance();
      System.out.println(o);

  }
}

El resultado de la ejecución es:

class com.jvm.test.MyTest1
com.jvm.test.MyTest1@61bbe9ba

Resultados del análisis: El
método findClass no imprimió nuestra entrada. La razón principal es que cuando el cargador de clases carga la MyTest1clase, primero delega en el cargador de clases padre (el cargador de clases padre de esta clase se configura como el cargador de clases del sistema) para cargar, el cargador de clases del sistema puede cargar la clase MyTest1 debajo del classpath, La razón directa es que el cargador de clases del sistema devuelve MyTest1, por lo que los métodos definidos por el cargador de clases no se ejecutan.

Si queremos usar nuestro propio cargador de clases, podemos modificar la ruta de recursos de la clase cargada y cargarla desde otras rutas:

public class ClassLoaderTest extends ClassLoader {

    private String classLoaderName;

    private static final String FILE_EXTENSION = ".class";

    private String path;

    public void setPath(String path) {
        this.path = path;
    }


    public ClassLoaderTest(String classLoaderName) {
        // 使用父类默认方式,将应用类加载器设置为该类加载器的父加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    public ClassLoaderTest(ClassLoader parentClassLoader, String classLoaderName) {
        // 显示指定该类的父加载器
        super(parentClassLoader);
        this.classLoaderName = classLoaderName;
    }


    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("find class execute!");
        byte[] data = loadClassData(className);
        return this.defineClass(className, data, 0, data.length);
    }

    private byte[] loadClassData(String className) {
        className = className.replace(".", "/");
        try (InputStream inputStream = new FileInputStream(new File(this.path+className + this.FILE_EXTENSION));
            ByteArrayOutputStream boas = new ByteArrayOutputStream()) {
            int ch = 0;
            while (-1 != (ch = inputStream.read())) {
                boas.write(ch);
            }
            return boas.toByteArray();
        } catch (Exception e) {
            e.getStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoaderTest classLoaderTest = new ClassLoaderTest("classLoaderTest");
        classLoaderTest.setPath("/Users/zhengyunwei/Desktop/");
        Class<?> clazz = classLoaderTest.loadClass("com.jvm.test.MyTest1");
        System.out.println(clazz);
        Object o = clazz.newInstance();
        System.out.println(o);

    }
}

El resultado de la ejecución después de la modificación del programa es:

find class execute!
class com.jvm.test.MyTest1
com.jvm.test.MyTest1@511d50c0

A partir del resultado de la ejecución, podemos saber que el método de carga de nuestro cargador de clases personalizado se carga mediante nuestro propio método de carga (nota: si existe bajo classPath, com.jvm.test.MyTest1.classdebe eliminarse; de ​​lo contrario, el programa se cargará primero desde classPath usando el cargador de clases del sistema).


4. El espacio de nombres del cargador de clases:

Código de muestra

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
       ClassLoaderTest classLoaderTest = new ClassLoaderTest("classLoaderTest");
       classLoaderTest.setPath("/Users/zhengyunwei/Desktop/");
       Class<?> clazz = classLoaderTest.loadClass("com.jvm.test.MyTest1");
       System.out.println(clazz.hashCode());
       System.out.println(clazz);
       ClassLoaderTest classLoaderTest2 = new ClassLoaderTest("classLoaderTest");
       classLoaderTest2.setPath("/Users/zhengyunwei/Desktop/");
       Class<?> clazz2 = classLoaderTest2.loadClass("com.jvm.test.MyTest1");
       System.out.println(clazz2.hashCode());
       System.out.println(clazz2);

   }

Resultados de ejecución:

find class execute!
1360875712
class com.jvm.test.MyTest1
find class execute!
491044090
class com.jvm.test.MyTest1

Análisis:
A partir de los resultados de la ejecución, se puede ver que la clase MyTest1se carga dos veces (el valor hash es diferente), el fenómeno que causó esta situación se debe principalmente al espacio de nombres del cargador de clases.

  • Cada cargador de clases tiene su propio espacio de nombres, que se compone de las clases cargadas por el cargador de clases y todos los cargadores principales.
  • En el mismo espacio de nombres, no aparecerán dos clases con el mismo nombre completo (incluido el nombre del paquete de la clase).
  • En diferentes espacios de nombres, puede haber dos clases con el mismo nombre completo (incluido el nombre del paquete de la clase).

5. Comprensión profunda del espacio de nombres:

Código:

public class NameSpaceTest {

    private NameSpaceTest nameSpaceTest;

    public void setNameSpace(Object o){
        this.nameSpaceTest = (NameSpaceTest)o;
    }

}

public class ClassLoaderNameSpaceTest {

    public static void main(String[] args) throws Exception {
    //  次处类加载器用了文章上面我们自定义的类加载器
        ClassLoaderTest classLoaderTest1 = new ClassLoaderTest("loader1");
        ClassLoaderTest classLoaderTest2 = new ClassLoaderTest("loader2");
        classLoaderTest1.setPath("/Users/zhengyunwei/Desktop/");
        classLoaderTest2.setPath("/Users/zhengyunwei/Desktop/");
        Class<?> clazz1 = classLoaderTest1.loadClass("com.jvm.test.NameSpaceTest");
        Class<?> clazz2 = classLoaderTest2.loadClass("com.jvm.test.NameSpaceTest");
        System.out.println(clazz1 == clazz2);
        Object obj1 = clazz1.newInstance();
        Object obj2 = clazz2.newInstance();
        Method method = clazz1.getMethod("setNameSpace",Object.class);
        method.invoke(obj1,obj2);
    }
}

El resultado de la ejecución es:

true

Análisis: Cuando se compila el proyecto de rutas de clases NameSpaceTest.classpresente, classLoaderTest1y classLoaderTest2se pondrá en marcha por las cargas cargador de clases sistema de la clase, por lo que la clase de objeto se carga sólo una vez, por lo que el resultado es verdadero.
Vamos a NameSpaceTest.classeliminar bajo classPath, jugará una copia al otro lado del camino que copiado en /Users/zyw/Desktop/com/jvm/testel directorio, y luego ir al programa ejecutado, el resultado es:

find class execute!
find class execute!
false
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.jvm.test.ClassLoaderNameSpaceTest.main(ClassLoaderNameSpaceTest.java:25)
Caused by: java.lang.ClassCastException: com.jvm.test.NameSpaceTest cannot be cast to com.jvm.test.NameSpaceTest
	at com.jvm.test.NameSpaceTest.setNameSpace(NameSpaceTest.java:15)
	... 5 more

Resultado del análisis:
Cuando nos classPathbajamos el NameSpaceTest.classborrado, entonces Loader de la clase para el cargador de clases delegado para cargar el sistema no carga, por lo que finalmente fue cargado por los cargadores de clases personalizadas, porque de classLoaderTest1y classLoaderTest2ninguna relación directa o indirecta padre-hijo, por lo que los dos se cargador cargador de carga en dos espacios de nombres diferentes, por lo que NameSpaceTestla clase de objeto se carga dos veces, y el mensaje de error java.lang.ClassCastException: com.jvm.test.NameSpaceTest cannot be cast to com.jvm.test.NameSpaceTest
es principalmente porque diferente espacio de nombres Los objetos de clase no son visibles.
Inserte la descripción de la imagen aquí
Conclusión

  • Las clases en el mismo espacio de nombres son mutuamente visibles.
  • El espacio de nombres del cargador de clases hijo contiene el espacio de nombres de todos los cargadores de clases padre. Por lo tanto, la clase cargada por el cargador de clases hijo puede ver la clase cargada por el cargador de clases padre. Por ejemplo, las clases cargadas por el cargador de clases del sistema pueden ver las clases cargadas por el cargador de clases raíz.
  • Las clases cargadas por el cargador de clases padre no se pueden ver como clases creadas desde el cargador de clases.
  • Si los dos cargadores de clases no tienen una relación padre-hijo directa o indirecta, entonces las clases que cargan son invisibles entre sí.

Inserte la descripción de la imagen aquí

41 artículos originales publicados · Me gustaron 14 · Visitantes más de 10,000

Supongo que te gusta

Origin blog.csdn.net/Yunwei_Zheng/article/details/104406604
Recomendado
Clasificación