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()方法。

getParent()方法

public final ClassLoader getParent()

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

getResource()方法

public URL getResource(String name)

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

返回URL对象,若找不到资源或没有权限,返回null。

上下文类加载器

上下文类加载器,为J2SE中引入的类委托方案提供一种替代方法。

当JVM核心类需要动态加载由开发人员提供的类或资源时,委托加载模型可能会遇到问题。

如,在JNDI中核心功能由rt.jar实现,这些JNDI类可以加载由供应商实现的JNDI程序。这就要求引导类加载器加载的类,可看见应用程序加载器加载的类。

JDK类加载器的可见性决定了无法使用委托模型加载,需要一种替代方案,使用线程上下文加载器实现。

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