Java类加载器委托模型

类加载器负责在运行时,将Java类动态加载到JVM,是JRE的一部分。

类加载器

常见的三种类加载器模型:

delegation

JDK默认的类加载器模型,特点,先委托父加载器,然后,再自己查找。- ask parent, then me.

post-delegation

常见于插件、servlet,或需要需要隔离的场景。 - ask me, then parent.

sibling

OSGi、Eclipse采用的类加载器模型。

JDK类加载器委托模型

1.检查该类是否已加载。

2.未加载时,请求父加载器加载该类。

3.父加载器未找到该类,抛出异常通知;尝试在自己的类路径中查找该类。

protected synchronized Class<?> loadClass(String name, boolean resolve) {
  //是否已加载
  Class<?> result = findLoadedClass(name);
  if (result == null) {
   try {
   //委托给父类。
    result = getParent().loadClass(name);
   } catch (ClassNotFoundException ex) {
   //父类查看未果,则在本地搜索。
    result = findClass(ex);
   }
  }
  //兼容老版本
  if (resolve) {
   resolveClass(result);
  }
  return result;
 }

Bootstrap类加载器

负责加载JDK内部的classes,通常是rt.jar和位于$ JAVA_HOME/jre/lib目录中的核心库。

扩展类加载器

扩展类加载器是引导类加载器(Bootstrap)的子类,负责装载Java标准扩展类,通常是$JAVA_HOME/lib/ext目录,或系统变量java.ext.dirs指定的值。

应用程序类加载器

应用程序类加载器,负责将所有应用程序级别的类加载到JVM中。

类加载器工作过程

当程序需要调用某个类时,类加载器会尝试使用全类名定位类文件。java.lang.ClassLoader.loadClass()方法,负责加载类定义到运行时环境,如果类尚未加载,将委托父类加载器,每个类加载器在收到加载请求时,都会先委托父类加载,直到委托请求到达Bootstrap加载器。每个加载器在父类加载不能完成时,调用java.net.URLClassLoader.findClass()方法在自己负责的范围内查找该类。

如果,最后一个类加载器也未能加载成功,抛出java.lang.NoClassDefFoundError或java.lang.ClassNotFoundException。

java.lang.ClassNotFoundException: com.xieyonghui.classloader.SampleClassLoader    
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)    
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)    
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)    
    at java.lang.Class.forName0(Native Method)    
    at java.lang.Class.forName(Class.java:348)

类加载器特性

委托模型

类加载器遵循委托模型,在请求查找类时,ClassLoader将类的搜索委托给父类加载器。假设要将一个应用程序中的类加载到JVM中,应用程序类加载器首先将该类的加载工作,委托给其父类(扩展类加载器),扩展类加载器又将其委托给引导类加载器。只有当引导类加载器和扩展类加载器,加载未成功后,应用程序类加载器才尝试自己去加载该类。

public void printClassLoaders() throws ClassNotFoundException {
 
    System.out.println("Classloader of this class:"+ PrintClassLoader.class.getClassLoader());
 
    System.out.println("Classloader of Logging:"+ Logging.class.getClassLoader());
 
    System.out.println("Classloader of ArrayList:"+ ArrayList.class.getClassLoader());
}

执行结果:

Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@3caeaf62
Class loader of ArrayList:null

唯一性

按照委托模型的推论,类加载器总是尝试向上委托,很容易确保类的唯一性。只父类加载器无法找到该类,子类加载器才会有机会加载。

可见性

父类加载器加载的类,对子类加载器是可见的。如,应用程序类加载器加载的类,可以看到扩展类加载器和Bootstrap类加载器加载的类,但反之不然。

如果类A由应用程序类加载器加载,类B由扩展类加载器加载,那么,所有经应用程序类加载器装入的类,都可以看到类A和类B。由扩展类加载器加载的类,却只能看到类B。

自定义ClassLoader

自定义一个从文件系统中加载类的ClassLoader,继承ClassLoader并覆写findClass()方法:

public class CustomClassLoader extends ClassLoader {
 
    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }
 
    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

loadClass()方法

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException 

加载指定的类,name参数引用完全限定的类名,将resolve设置为true,loadClass()方法将解析类引用。如果,只想确定该类是否存在,则把resolve参数设置为false。

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException {
     
    synchronized (getClassLoadingLock(name)) {
        // 首先, 检查这个类是否已经加载过了
        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
                }
 
                if (c == null) {
           //如果仍没有找到,则调用findClass,查找该类
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

方法按以下顺序搜索类:

1、调用findLoadedClass(String)方法以查看类是否已加载。

2、调用父加载器上loadClass(String)方法。

3、调用findClass(String)方法查找类。

defineClass()方法

protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

方法负责将字节转换为类实例,如果不存在有效类,则会抛出ClassFormatError。该方法无法被覆盖。

findClass()方法

protected Class<?> findClass(String name) throws ClassNotFoundException

方法以全类名为参数查找类,自定义类加载器需要覆盖该方法。实现遵循委托模型。在父加载器找不到类时,会调用loadClass()方法。父级没有找到类时默认会抛出ClassNotFoundException。

getParent()方法

public final ClassLoader getParent()

方法返回父类加载器以执行委派。

getResource()方法

public URL getResource(String name)

此方法尝试查找指定名称的资源。首先委托父加载器。如果父项为null,则搜索内置到虚拟机中类加载器的路径。如果失败,该方法将调用findResource(String)查找资源。参数中类路径可是相对或绝对路径。

返回一个资源的URL对象,如果找不到资源或没有权限,返回null。

上下文类加载器

上下文类加载器,为J2SE中引入的类委托方案提供了一种替代方法。当JVM核心类需要动态加载,由开发人员提供的类或资源时,委托加载模型可能会遇到问题。如,在JNDI中,核心功能由rt.jar实现,这些JNDI类,可以加载由供应商实现的JNDI程序。这种场景就要求引导类加载器加载的类,可以看见应用程序加载器加载的类。类的可见性决定了无法使用委托模型加载,需要一种替代方案,使用线程上下文加载器来实现。

java.lang.Thread类有一个方法getContextClassLoader(),为指定的线程返回一个ContextClassLoader,在加载资源和类时,ContextClassLoader由线程的创建者提供。