Class loader in-depth analysis and analysis of important features (3)

1. Class Loader

The class loader is used to load classes into the Java virtual machine. Starting from JDK 1.2, the class loading process uses a dual delegation mechanism, which can better ensure the security of the Java platform. In this delegation mechanism, in addition to the root class loader that comes with the Java virtual machine, the rest of the class loaders have only one parent loader. When the Java program requests Loader1 to load the class Sample, it first entrusts the parent class loader to load it, and returns if it can be loaded, otherwise the Loader1 loader loads the Sample.


2. Types of class loaders

  • The class loader that comes with the Java virtual machine
  • Root class loader (BootStrap):
    This class loader has no parent class loader and is implemented by a virtual machine. It is mainly responsible for loading the core class library of the virtual machine, such as: java.lang.*etc. The root loader sun.boot.class.pathloads the class library from the directory specified by the system properties . The underlying implementation of the root loader mainly depends on the underlying operating system and is part of the virtual machine implementation. It does not inherit the java.lang.ClassLoader class.

  • Extension class loader (Extension):
    The parent loader of this class loader is the root loader. It java.ext.dirsloads the class library from the directory specified by the system properties, or loads the class library from a jre/lib/extsubdirectory (extension directory) of the jdk installation directory . If the jar created by the user is placed in this directory, it will also be automatically loaded by the extension class器 载。 Loader. The extension class loader is a pure Java class and is a subclass of java.lang.ClassLoader

  • System (application) class loader (System):
    The parent loader of this class loader is an extension class loader. It loads classes from the environment variable classpath or the directory specified by the system property java.class.path. It is the default parent loader of the user-defined class loader. The application class loader is a pure Java class, which is java.lang.ClassLoader Subclass

  • User-defined class loader
  • java.lang.ClassLoaderSubclass
  • Users can customize the loading method of the class need to inheritjava.lang.ClassLoader

Extension point:

  • The JVM specification allows a class loader to pre-load a class when it is expected to be used. If a .classfile is missing or there is an error during the pre-loading process , the class loader must 程序首次主动使用report an error (LinkageError) when the class is in use.
  • If this class has not been actively used by the program, then the class loader will not report an error.

The loading flow chart of the class loader:
Insert picture description here


3. Custom class loader code example

Code:

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);

  }
}

The execution result is:

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

Analysis results: The
findClass method did not print out our input. The main reason is that when the class loader loads the MyTest1class, it is first delegated to the parent class loader (the parent class loader of this class is set as the system class loader) to load, the system class loader can load the MyTest1 class under the classpath, so The direct reason is that the system class loader returns MyTest1, so the methods defined by the class loader are not executed.

If we want to use our own class loader, we can modify the resource path of the loaded class and load it from other paths:

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);

    }
}

The execution result after the program modification is:

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

From the execution result, we can know that the loading method of our custom class loader is loaded by our own loading method (note: if it exists under the classPath, it com.jvm.test.MyTest1.classneeds to be deleted, otherwise the program will first be loaded from the classPath using the system class loader).


4. The namespace of the class loader:

Sample code

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);

   }

Results of the:

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

Analysis:
From the execution results, it can be seen that the class MyTest1is loaded twice (hash value is different), the phenomenon that caused this situation is mainly due to the namespace of the class loader.

  • Each class loader has its own namespace, which is composed of the classes loaded by the class loader and all parent loaders.
  • In the same namespace, no two classes with the same complete name (including the package name of the class) will appear.
  • In different namespaces, there may be two classes with the same complete name (including the package name of the class).

5. In-depth understanding of the namespace:

Code:

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);
    }
}

The execution result is:

true

Analysis: When the project is compiled Classpath NameSpaceTest.classpresent, classLoaderTest1and classLoaderTest2will be commissioned by the system class loader loads the class, so the class object is loaded only once, so the result is true.
We will NameSpaceTest.classdelete under classPath, will play a copy to the other side of the path I copied in /Users/zyw/Desktop/com/jvm/testthe directory, and then go to the program executed, the result is:

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

Result Analysis:
When we classPathlower the NameSpaceTest.classdeletion, then we custom class loader to delegate class loader to load the system will not load, so we eventually went loaded by custom class loaders, because we of classLoaderTest1and classLoaderTest2no direct or indirect parent-child relationship, so the two would loader loader to load in two different namespaces, so NameSpaceTestthe class object is loaded twice, and the error message java.lang.ClassCastException: com.jvm.test.NameSpaceTest cannot be cast to com.jvm.test.NameSpaceTest
is mainly because different namespace Class objects are not visible.
Insert picture description here
in conclusion:

  • Classes in the same namespace are mutually visible.
  • The namespace of the child class loader contains the namespace of all parent class loaders. Therefore, the class loaded by the child class loader can see the class loaded by the parent class loader. For example, the classes loaded by the system class loader can see the classes loaded by the root class loader.
  • Classes loaded by the parent class loader cannot be viewed as classes built from the class loader.
  • If the two class loaders do not have a direct or indirect parent-child relationship, then the classes they load are invisible to each other.

Insert picture description here

Published 41 original articles · Liked 14 · Visitors 10,000+

Guess you like

Origin blog.csdn.net/Yunwei_Zheng/article/details/104406604