类加载器以及如何打破双亲委派机制

java里有如下几种类加载器:

引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,如:rt.jar、charsets.jar等等

扩展类加载器:负责加载支持JVM运行的位于jre目录下的ext扩展目录中的jar包

应用程序类加载器:负责加载classpath路径下的类包,主要就是加载开发人员编写的程序代码类

自定义加载器:负责加载自定义类路径下的类文件

双亲委派机制

类加载机制的委派层级结构如下:

 

类加载器以及如何打破双亲委派机制_第1张图片

其实这个加载过程就包含一个双亲委派机制。这个委派机制的大致流程是:

  1. 先判断当前加载器是否已加载目标类,如已加载直接返回,如未加载则委托父加载器加载;

  2. 父类加载器拿到目标类后先判断自己是否已加载目标类,如已加载则返回该类,如未加载再委托自己的父加载器加载。如此递归循环,直到当前类加载器没有父加载器(即当前加载器为引导类加载器),这时交由引导类加载器去加载;

  3. 引导类加载器加载目标类时现在自己的类路径下找寻目标类文件,如找到了则直接加载该类,如未找到则向下委托子类加载器去加载。直到找到该类文件且成功加载为止

我们以加载User对象为例,流程图如下: 

 

双亲委类加载器以及如何打破双亲委派机制_第2张图片派机制说简单点就是:逐级向上委托父加载器加载直到引导类加载器,然后再逐级向下委托子加载器加载

接下来我们结合上面的流程图再来看下相关的核心源码:

protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
   {
// 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.
         //调用URLClassLoader的findClass方法在当前类加载器的类路径中寻找目标类文件,
        c = findClass(name);
    }
}
if (resolve) {
    resolveClass(c);
}
return c;
   }

设计双亲委派模型的目的什么?

沙箱安全机制:防止核心API库被随意篡改

保证被加载类的唯一性:在这种委派机制下,同一应用中的class文件永远只会加载一次。

如何打破双亲委派机制?

类加载器两大核心方法:loadClass(String name) 和 findClass(String name),其中双亲委派机制就是通过递归调用loadClass方法来实现的。我们只需有针对性的重写该方法即可打破该委派模型,首先我们自定义一个类加载器:CustClassLoader.class

package com.htc.jvm;
​
import java.io.FileInputStream;
​
/**
 * @author: htc
 * @date: 2020/10/22.
 * @descr: 自定义类加载器-打破双亲委派机制
 */
public class CustClassLoader extends ClassLoader {
    private String classPath;
​
    public CustClassLoader(String classPath) {
        this.classPath = classPath;
    }
​
    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }
​
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
​
    /**
     * 重写类加载方法,实现自己的类加载逻辑,打破双亲委派
     *
     * @param name
     * @param resolve
     * @return
     */
    @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) {
                /**
                 * 指定自定义类的加载方式,其他类走双亲委派机制
                 */
                if (!name.startsWith("com.htc.jvm")) {
                    c = this.getParent().loadClass(name);
                } else {
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

再创建一个测试类:

public class TestCustClassCloader {
    public static void main(String[] args) throws Exception {
        String classPath ="D:\\tmpPath";
        CustClassLoader classLoader = new CustClassLoader(classPath);
        Class clazz = classLoader.loadClass("com.htc.jvm.Test");
​
        //通过反射实例化该对象
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sou", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

在执行测试了之前我们需在D:\tmpPath\com\htc\jvm目录下创建Test.java文件,并通过javac命令对其进行编译,编译后的Test.class文件也存放在该路径下。然后再执行测试类。控制台输出如下:

Connected to the target VM, address: '127.0.0.1:54493', transport: 'socket'
this is sou method !
com.htc.jvm.CustClassLoader
Disconnected from the target VM, address: '127.0.0.1:54493', transport: 'socket'
​
Process finished with exit code 0

且通过断点调试我们可以发现,在加载Test类的时候,CustClassLoader类加载器并没有委托给父类加载,而是自身直接加载Test类!

你可能感兴趣的:(jvm从入门到绝望,java)