JVM类加载机制-双亲委派机制

109 阅读3分钟

什么是双亲委派机制

image.png

  • 一个类在加载时,类加载器会先判断是否已经加载过这个类,如果没有,就往上向父类加载器传递,调用父类加载器的的loadClass方法来加载。当父类加载器无法完成加载任务的时候,子类再加载该类。这样做的用意在哪里呢?
  • 比如现在有一个应用类,在双亲委派机制下,第一次加载时,先传递到Bootstrap,发现Bootstrap无法加载后,往下传递到Extension中加载。发现Extension无法加载后,往下传递到Application中加载。在第二次加载时,发现已经在Application中加载过了,就直接由Application加载。如果没有双亲委派机制,那么每次都需要从上而下尝试加载一遍。
  • 双亲委派机制下,第一次,Application->Extension->Bootstrap->Extension->Application;第二次,Application。
  • 不使用双亲委派机制,第一次,Bootstrap->Extension->Application;第二次Bootstrap->Extension->Application。

实现代码

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}
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;
    }
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

自定义类加载器

  • 从代码中看到,双亲委派下,类的加载过程代码实现: 1、findLoadedClass来判断是否加载过 2、如果没有加载过,判断父类加载器是否为空,若为空,用bootstrap的findBootstrapClassOrNull来加载。 3、如果以上步骤依旧没有加载成功if(c==null)。那就使用当前类的findClass方法去加载。

  • 在自定义类加载器时,一般都是继承ClassLoader,重写findClass()方法

  • 如果需要打破双亲委派,则需要重写loadClass

static class MyClassLoader extends ClassLoader{
    private String classpath;

    public MyClassLoader(String classpath){
        this.classpath = classpath;
    }

    //com.entity.user->com/entity/user.class
    private byte[] getByte(String name) throws Exception {
        name = name.replaceAll("\.","/");
        FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");
        int len = fileInputStream.available();
        byte[] data = new byte[len];
        fileInputStream.read(data);
        fileInputStream.close();
        return data;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = getByte(name);
            return defineClass(name,data,0,data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    @Override
    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();


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

                    //// 如果不是load开头的,交由父加载,是的话,自己加载
                    if(!name.startsWith("load")){
                        c = this.getParent().loadClass(name);
                    }else{
                        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;
        }
    }

}
  • 准备三个测试文件
//项目中
public class Game {
    private String name;
    ...
    public void play(){
        System.out.println("pokemon");
    }
}
//test1中
public class Game {
    private String name;
    ...
    public void play(){
        System.out.println("Mario");
    }
}
//test2中
public class Game {
    private String name;
    ...
    public void play(){
        System.out.println("Zelda");
    }
}
  • 测试代码:
public static void main(String[] args) throws Exception {
    Class clazz1 = Game.class;
    System.out.println(clazz1.getClassLoader().getClass().getName());
    Object object1 = clazz1.newInstance();
    Method method1 = clazz1.getMethod("play",null);
    method1.invoke(object1,null);

    MyClassLoader myClassLoader = new MyClassLoader("/Users/d/Desktop/byfile/test1");

    Class clazz2 = myClassLoader.loadClass("load.Game");
    System.out.println(clazz2.getClassLoader().getClass().getName());
    Object object2 = clazz2.newInstance();
    Method method2 = clazz2.getMethod("play",null);
    method2.invoke(object2,null);

    MyClassLoader myClassLoader1 = new MyClassLoader("/Users/d/Desktop/byfile/test2");
    Class clazz3 = myClassLoader1.loadClass("load.Game");
    System.out.println(clazz3.getClassLoader());
    Object object3 = clazz3.newInstance();
    Method method3 = clazz3.getMethod("play",null);
    method3.invoke(object3,null);

}
  • 测试结果:

image.png

  • 结果分析: 可以看到Game在被AppClassLoader加载过后,在之后加载时,依旧使用了自定义类加载器。 可能有同学会有疑问,自己写的类默认继承的Object也会被自定义类加载器加载吗?看loadClass中的这段代码:
if(!name.startsWith("load")){
    c = this.getParent().loadClass(name);
}else{
    c = findClass(name);
}

由于自定义加载器无法加载Object类,在打破双亲委派机制时,需要选择性的跳过,只有自己写的类才被自定义类加载器加载,其他的依旧需要jvm默认的加载器来加载。当把上面这段代码去掉运行后,程序会报错: java.io.FileNotFoundException: /Users/d/Desktop/byfile/test1/java/lang/Object.class (No such file or directory)