jvm 类加载机制(二)【类加载器及双亲委派模型】

类加载器及双亲委派模型


前言:首先列出我前面提到的三个问题。1.jvm什么时候会去加载Class文件并初始化类呢?2. jvm是如何加载Class文件的呢?3. jvm加载一个Class文件要经过哪些步骤呢?上一篇博文我们已经阐述了第一个问题 类加载的时机,本文主要阐述第二个问题。

一 类加载器


在类加载的第一个阶段“加载”阶段,需要通过一个类的全限定名来获取定义此类的二进制字节流,完成这个动作的就是类加载器。这一动作是放在Java虚拟机外部去实现的,以便让应用程序自己决定如何获取所需的类。

JVM预定义的三种类型类加载器:

  • 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 Java_Home/lib 下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
  • 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home /lib/ext 或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
  • 系统(System)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。

除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。

二 双亲委派模型


双亲委派机制描述:
       某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
双亲委派模型

下面我们可以从源码来深入理解双亲委派机制:

 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) {
                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.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

三 自定义类加载器

从上面对于java.lang.ClassLoader的loadClass(String name, boolean resolve)方法的解析来看:

1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可。

2、如果想打破双亲委派模型,那么就重写整个loadClass方法。

当然,我们自定义的ClassLoader不想打破双亲委派模型,所以自定义的ClassLoader继承自java.lang.ClassLoader并且只重写findClass方法。

package aboutJvm;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader{

    //.class文件所在路径
    private String path = "F:\\test";

    public MyClassLoader(){

    }

    public MyClassLoader(ClassLoader parent){
        super(parent);
    }


    /**
     * 重写findClass方法
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);
        if(data == null){
            throw new ClassNotFoundException();
        }else{
            return this.defineClass(name, data, 0, data.length);

        }
    }

    /**
     * 获取类的class文件的字节数组 
     */
    public byte[] loadClassData(String className){

        String fileName = path + File.separatorChar
                + "Person.class";

        System.out.println("fileName: "+fileName);

        try {
            FileInputStream fis = new FileInputStream(new File(fileName));
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while((b = fis.read())!=-1){
                baos.write(b);
            }
            return baos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();
        }

        return null;

    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> clazz = myClassLoader.loadClass("aboutJvm.Person");//和MyClassLoader类同包
        Object obj = clazz.newInstance();
        System.out.println("类加载器: "+obj.getClass().getClassLoader());
    }
}
/**
OutPut:
    类加载器:sun.misc.Launcher$AppClassLoader@5736ab79
*/

       由输出结果可见,程序并没有调用我们自己定义的类加载器。这是因为在CLASSPATH下有Person.class,那么自然是由Application ClassLoader来加载这个.class文件了。
      解决办法:

  • 把编译后的Person.class文件拷贝到F:\test 目录下,并且删除CLASSPATH下的Person.class。此时Application ClassLoader就会把这个.class文件交给下一级用户自定义ClassLoader去加载了。
  • main方法中的第一行这么写:
    MyClassLoader myClassLoader = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent());
    即把自定义ClassLoader的父加载器设置为Extension ClassLoader,这样父加载器加载不到Person.class,就交由子加载器MyClassLoader来加载了。
此时output:
    fileName: F:\test\Person.class
    类加载器: aboutJvm.MyClassLoader@2d7fc1e7

编写自定义类加载器遇到的问题:java.lang.NoClassDefFoundError,这篇博文很好的解决了我的问题。
https://www.cnblogs.com/chenjfblog/p/7904024.html

猜你喜欢

转载自blog.csdn.net/try_try_try/article/details/80064390