由源码深入Java类加载器(双亲委派模型)

JVM类加载器

JVM主要有以下几种类加载器:

  1. 引导类加载器
    主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类。
  2. 扩展类加载器
    主要加载JVM中扩展类,位于JRE的ext目录下。
  3. 应用程序类加载器
    主要负责加载ClassPath路径下的类,也就是业务类。
  4. 自定义加载器
    负责加载用户自定义路径下的类。

类加载器关系

在这里插入图片描述

源码解析

ExtClassLoader和AppClassLoader的创建流程

先看下Launcher的构造方法:

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        	//获取扩展类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        
        try {
        	//获取应用类加载器,this.loader就是默认的类加载器:即AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //设置默认classLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
    }
ExtClassLoader

看下ExtClassLoader的获取方法getExtClassloader():
可以看到ExtClassLoader是Launcher的一个内部类,继承的是URLClassLoader。

public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
			//获取要加载的类文件
            final File[] var0 = getExtDirs();

            try {
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        int var1 = var0.length;

                        for(int var2 = 0; var2 < var1; ++var2) {
                            MetaIndex.registerDirectory(var0[var2]);
                        }
						//new一个ExtClassLoader
                        return new Launcher.ExtClassLoader(var0);
                    }
                });
            } catch (PrivilegedActionException var2) {
                throw (IOException)var2.getException();
            }
        }

查看getExtDirs()方法:可以看到要加载的类文件都是位于ext文件夹下的。

private static File[] getExtDirs() {
            String var0 = System.getProperty("java.ext.dirs");
            File[] var1;
            if (var0 != null) {
                StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
                int var3 = var2.countTokens();
                var1 = new File[var3];

                for(int var4 = 0; var4 < var3; ++var4) {
                    var1[var4] = new File(var2.nextToken());
                }
            } else {
                var1 = new File[0];
            }

            return var1;
        }

继续看ExtClassLoader的构造方法:

  public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

调用了父类的构造方法:
可以看到ExtClassLoader的parent赋值为null,因为引导类加载器是C++语言写的,没有实际java对象。

public URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        acc = AccessController.getContext();
        ucp = new URLClassPath(urls, factory, acc);
    }

这样一个ExtClassLoader就创建好了。

AppClassLoader

AppClassLoader同样也是继承了URLClassLoader类
看下getAppClassLoader方法:

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }

可以看到,getAppClassLoader主要加载工程classPath下的类文件。
继续看getAppClassLoader构造方法:

AppClassLoader(URL[] var1, ClassLoader var2) {
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }

从一开始的Launcher构造方法中可以看到参数var2就是先初始化的extClassLoader。
同样调用了父类URLClassLoader的构造,将extClassLoader设置为parent,所以appClassLoader的parent是extClassLoader。

由此三个主要类加载器之间的关系弄清楚了,各自要加载的范围也弄清楚。我们再看看自定义类加载器的实现。

自定义类加载器

自定义类加载器要继承ClassLoader方法,只需要重写findClass方法就行了:

package classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/**
 * @author zhw
 * @description
 * @date 2021-07-15 14:36
 */
public class MyClassLoader extends ClassLoader{

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");
        try{
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> c = defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    private byte[] getClassBytes(File file) throws Exception
    {
        FileInputStream inputStream = new FileInputStream(file);//原始输入流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1 ) {
            baos.write(buffer, 0, len);
        }
        baos.flush();
        return baos.toByteArray();
    }
}

关于自定义类加载器的parent是谁,可以查看:

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

继续看getSystemClassLoader():

public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
            
            }
            sclSet = true;
        }
    }

 public ClassLoader getClassLoader() {
        return this.loader;
    }

返回的是this.loader。上面已经知道loader就是AppClassLoader。所以自定义类加载器的默认parent就是AppClassLoader。

双亲委派

在类加载流程中,首先调用的是Launcher.loader.loadClass()方法。

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        	//获取扩展类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        
        try {
        	//获取应用类加载器,this.loader就是默认的类加载器:即AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //设置默认classLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
    }

loader就是AppClassLoader。所以继续看AppClassLoader.loadClass方法:

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
            int var3 = var1.lastIndexOf(46);
            if (var3 != -1) {
                SecurityManager var4 = System.getSecurityManager();
                if (var4 != null) {
                    var4.checkPackageAccess(var1.substring(0, var3));
                }
            }

            if (this.ucp.knownToNotExist(var1)) {
                Class var5 = this.findLoadedClass(var1);
                if (var5 != null) {
                    if (var2) {
                        this.resolveClass(var5);
                    }

                    return var5;
                } else {
                    throw new ClassNotFoundException(var1);
                }
            } else {
            	//调用父类的loadClass方法
                return super.loadClass(var1, var2);
            }
        }

继续看super.loadClass(var1, var2):双亲委派机制的核心代码来了

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 {
                	//如果父加载器不为null,则交给父加载器加载。
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else { //如果父加载器为null,则交给引导类加载器加载。
                        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;
        }
    }

看完上面的代码后,是不是觉得双亲委派机制的实现很简单?
在这里插入图片描述
双亲委派的作用:

  1. 沙箱安全,保证JVM核心代码不被用户自定义类覆盖。
  2. 保证了类加载的唯一性。

如何打破双亲委派?

看双亲委派机制的源码,可以看到主要实现实在loadClass方法中,那么,只需要重写loadClass(String name, boolean resolve)方法即可:

package classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/**
 * @author zhw
 * @description
 * @date 2021-07-15 14:36
 */
public class MyClassLoader extends ClassLoader{

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");
        try{
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> c = defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    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("hiwei.test.Person".equals(name)){
                    c = findClass(name);
                }else{
                    c = getParent().loadClass(name);
                }
                
                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;
        }
    }

    private byte[] getClassBytes(File file) throws Exception
    {
        FileInputStream inputStream = new FileInputStream(file);//原始输入流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1 ) {
            baos.write(buffer, 0, len);
        }
        baos.flush();
        return baos.toByteArray();
    }
}

测试类:

package classload;
/**
 * @author zhw
 * @description
 * @date 2021-07-15 15:09
 */
public class ClassLoadTest {
    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> clazz = Class.forName("hiwei.test.Person", true, myClassLoader);
        Object o = clazz.newInstance();
        System.out.println(o.toString());
        System.out.println(clazz.getClassLoader());
    }
}

测试:
目标文件夹和classPath都存在Person.class

  1. 测试一:
    结果:使用自定义加载器加载。
    在这里插入图片描述
  2. 测试二:不覆盖loadClass方法。
    结果:使用AppClassLoader
    在这里插入图片描述

破坏双亲委派的应用

tomcat破环双亲委派

在这里插入图片描述
在tomcat中不同的应用可能依赖同一个jar的不同版本,如果共用一个类加载器,会导致无法进行环境隔离。所以tomcat自定义类加载器,每个应用都有自己的类加载器,负责加载自己应用下的类,打破了双亲委派机制,不在让父加载器先加载。

源码分析

tomcat的Bootstrap.initClassLoaders()方法中会初始化tomcat核心类的类加载器:

	private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

这三个类加载器并未破坏双亲委派模型,这三个都是URLClassLoader的实例。
真正破坏双亲委派模型的是WebappClassLoader类加载器,WebappClassLoader继承了WebappClassLoaderBase,而WebappClassLoaderBase重写了loadClass方法:

@Override
    //todo 此处破坏了双亲委派模型
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;

            // Log access to stopped class loader
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
		//省略,,,,
    }

可以看到,重写的loadClass方法破坏了双亲委派模型。

JDBC破坏双亲委派

原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的jar中的Driver类具体实现的。
以以下版本为例:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>

Driver实现类:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

可以看到,使用了DriverManager类。在DriverManager类中有静态代码块:

	static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

继续看loadInitialDrivers()

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

看下面方法:

 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

使用了当前线程的classLoader。

	private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

回到loadInitialDrivers()方法,继续往下看:

AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                //加载Driver.class
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

进入loadedDrivers.iterator():

public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();
            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }
            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

可以看到返回了一个重写了hasNext()和next()方法的匿名Iterator类。

try{
        while(driversIterator.hasNext()) {
            driversIterator.next();
           }
	 } 

在这里调用的都是重写方法。
由调用关系,最终可以看到下面的方法:

		private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                    	//找到Driver.calss
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
            	//加载Driver.calss
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

可以看到,Driver.class是在hasNextService()中取到,nextService()中加载的:

c = Class.forName(cn, false, loader);

这里的类加载器loader就是上面的

ClassLoader cl = Thread.currentThread().getContextClassLoader();

现在真相大白了,在使用spi机制时,会使用当前线程的类加载器加载"META-INF/services/"下面的Driver.class。
在双亲委派模型下,类的加载是由下至上委托的,jdk无法加载其它文件夹下的类文件。但是在jdbc中,Driver要由供应商实现,所以需要进行加载,在spi使用方法中,使用线程上下文类加载器加载指定路径下的Driver.class文件,解决了这个问题。
JDBC破坏双亲委派的实现是使用父加载器加载指定路径下的class文件。

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hi wei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值