Home  >  Article  >  Java  >  Detailed explanation of java class loader ClassLoader

Detailed explanation of java class loader ClassLoader

angryTom
angryTomforward
2019-11-27 15:35:502285browse

Detailed explanation of java class loader ClassLoader

Ways to get ClassLoader

1. Get the ClassLoader of the current class

clazz.getClassLoader()

2. Get the ClassLoader of the current thread context

Thread.currentThread().getContextClassLoader();

3. Get the system’s ClassLoader

ClassLoader.getSystemClassLoader()

4. Get the caller’s ClassLoader

DriverManager.getCallerClassLoader

ClassLoader source code analysis

(Recommended learning: Java Video Tutorial)

Overview

The class loader is used to load classes Object, ClassLoader is an abstract class. If we are given the binary name of a class, the class loader should try to locate or generate the data that makes up the defining class. A typical strategy is to convert a given binary name into a file name, and then read the class file corresponding to this file name from the file system.

Each Class object will contain a reference to the ClassLoader that defines it.

The Class object of the array class is not created by the class loader, but is automatically created by the JVM as needed during the Java runtime. For the class loader of the array class, it is returned through Class.getClassLoader(), which is the same as the class loader of the element type in the array; if the element type in the array is a native type, The array class does not have a class loader [Code 1].

The application implements a subclass of ClassLoader to extend the way the JVM dynamically loads classes.

Class loaders are typically used by security managers to identify security domain issues.

The ClassLoader class uses a delegation model to find classes and resources. Each instance of ClassLoader will have a parent ClassLoader associated with it. When ClassLoader is asked to find a class or resource, the ClassLoader instance tries on its own Before searching for a class or resource, its parent class loader will be delegated to complete it. The built-in class loader of the virtual machine, called the startup class loader, does not have a parent loader, but it can be used as the parent class loader of a class loader [Parental delegation mechanism].

A class loader that supports concurrent class loading is called a parallel class loader. It is required to register itself through the ClassLoader.registerAsParallelCapable method during initialization. The ClassLoader class is registered as parallel by default, but if it If the subclasses are also loaded in parallel, you need to register the subclasses separately.

In an environment where the delegation model is not strictly hierarchical, the class loader needs to be parallel, otherwise class loading will cause deadlock, because the loader's lock is always held during the class loading process.

Normally, the Java virtual machine loads classes from the local file system in a platform-dependent manner. For example, in UNIX systems, the virtual machine loads classes from the directory defined by the CLASSPATH environment.
However, some classes do not come from files; they are obtained from other sources, such as the network, or [dynamic proxies] are built by the application itself. The defineClass(defineClass) method will convert the byte array into an instance of Class. An instance of this newly defined class can be created by Class.newInstance.

The methods and constructors of objects created by a class loader may reference other classes. In order to determine the referenced class, the Java virtual machine calls the loadClass of the class loader that originally created the class. method.

Binary name: Any class name provided to CalssLoader in the form of a string parameter must be a binary name, including the following four situations

  • "java.lang. String" Normal class
  • "javax.swing.JSpinner$DefaultEditor" Internal class
  • "java.security.KeyStore\(Builder\)FileBuilder$1" Internal of KeyStore The inner class of class Builder The first anonymous inner class of FileBuilder
  • "java.net.URLClassLoader$3$1" The third anonymous inner class of URLClassLoader class The first anonymous inner class

Code 1:

public class Test12 {
    public static void main(String[] args) {
        String[] strings = new String[6];
        System.out.println(strings.getClass().getClassLoader());
        // 运行结果:null

        Test12[] test12s = new Test12[1];
        System.out.println(test12s.getClass().getClassLoader());
        // 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
        // 运行结果:null
    }
}

loadClass method

The source code of loadClass is as follows. The loadClass method loads the Class with the specified binary name. By default, it is in the following order. Find a class:

a) Call findLoadedClass(String) to check whether this class is loaded

b) Call the loadClass method of the parent class loader. If the parent class loader is null, it will be called Start the class loader

c) Call the findClass(String) method to find

Use the above steps if the class is found and resolve is true, the resolveClass(Class) method will be called

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
      // First, check if the class has already been loaded
      Class<?> c = findLoadedClass(name);
      if (c == null) {
          long t0 = System.nanoTime();
          try {
              if (parent != null) {
                  c = parent.loadClass(name, false);
              } else {
                  c = findBootstrapClassOrNull(name);
              }
          } catch (ClassNotFoundException e) {
              // ClassNotFoundException thrown if class not found
              // from the non-null parent class loader
          }

          if (c == null) {
              // If still not found, then invoke findClass in order
              // to find the class.
              long t1 = System.nanoTime();
              c = findClass(name);

              // this is the defining class loader; record the stats
              sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
              sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
              sun.misc.PerfCounter.getFindClasses().increment();
          }
      }
      if (resolve) {
          resolveClass(c);
      }
      return c;
  }
}

findClass method

findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass方法

defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

自定义类加载器

/**
 * 继承了ClassLoader,这是一个自定义的类加载器
 * @author 夜的那种黑丶
 */
public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class<?> clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 类加载器名称,标识作用
     */
    private String classLoaderName;

    /**
     * 从磁盘读物字节码文件的扩展名
     */
    private String fileExtension = ".class";

    /**
     * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(String classLoaderName) {
        // 将系统类加载器当做该类加载器的父加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 创建一个类加载器对象,显示指定该类加载器的父加载器
     * 前提是需要有一个类加载器作为父加载器
     * @param parent 父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 显示指定该类加载器的父加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制
     * 该方法会在检查完父类加载器之后被loadClass方法调用
     * 默认返回ClassNotFoundException异常
     * @param className 类名
     * @return Class的实例
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        /*
         * 通过defineClass方法将字节数组转换为Class
         * defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析
         */
        return this.defineClass(className, data, 0 , data.length);
    }

    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

以上是一段自定义类加载器的代码,我们执行这段代码

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。

改造以下代码,添加一个path属性用来指定类加载位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 从指定路径加载
     */
    private String path;

    ......
    
    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

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

运行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下测试代码,并删除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

运行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。

再来测试一块代码

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

再改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。

我们可以通过设置父类加载器来让loader和loader2处于同一命名空间

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

扩展:命名空间

1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成

2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类

3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

php中文网,大量的免费Java入门教程,欢迎在线学习!  

The above is the detailed content of Detailed explanation of java class loader ClassLoader. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete