New to Nutbox?

【JVM】02. 类加载(二):类加载器

0 comments

fangwei
53
12 days agoSteemit3 min read

在前两篇博文中,我们对JVM的整体架构进行了概览,并深入探讨了类加载器子系统中的类加载过程。本文将聚焦于类加载器本身,为后续介绍双亲委派机制做铺垫。

1 类加载器类型

类加载过程主要是由类加载器实现,在JVM中类加载器根据类的加载路径实现了以下4种类加载器:

  1. BootstrapClassLoader(启动类加载器): 最顶层的加载类,由 C++实现,主要用来加载 JDK 内部的核心类库( jre/lib/rt.jar)以及被 -Xbootclasspath参数指定的路径下的所有类。
  2. ExtensionClassLoader(扩展类加载器): 平台类加载器,负责加载扩展jar包,主要位于jre/lib/ext/.jar。
  3. AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。如果应用中没有自定义的类加载器,一般情况下默认使用这个类加载器。
  4. 自定义加载器: 负责加载用户自定义路径下的类包。

不同类加载器之间通过parent属性形成父子关系,这种关系对于双亲委派机制至关重要。整体关系如下:

image.png

2 类加载器的初始化过程

类加载器的初始化过程是JVM启动和类加载过程的基础。

image.png

BootstrapClassLoader是在启动Java虚拟机时由底层C++代码创建。ExtClassLoaderAppClassLoader则是由JVM启动器实例sun.misc.Launcher类在构造时进行创建,Launcher类是由BootstrapClassLoader负责加载,以下是相关核心代码:

public class Launcher {
    private static Launcher launcher = new Launcher();  
    private static String bootClassPath = System.getProperty("sun.boot.class.path"); 
    private ClassLoader loader;
    
    //Launcher的构造方法
    public Launcher() {  
        Launcher.ExtClassLoader var1;  
        try {  
            //构造ExtClassLoader,在构造的过程中将其父加载器设置为null
            var1 = Launcher.ExtClassLoader.getExtClassLoader();  
        } catch (IOException var10) {  
            throw new InternalError("Could not create extension class loader", var10);  
        }  
      
        try {  
            //构造AppClassLoader,在构造的过程中将其父加载器设置为ExtClassLoader
            //Launcher的loader属性值是AppClassLoader,我们一般就是用这个类加载器来加载我们自己写的应用程序
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);  
        } catch (IOException var9) {  
            throw new InternalError("Could not create application class loader", var9);  
        }  
      
        Thread.currentThread().setContextClassLoader(this.loader);  
      
    }
}
  1. sun.misc.Launcher初始化使用了饿汉的单例模式,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。
  2. 在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。
  3. JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。

3 自定义类加载器

自定义类加载器可以用于实现特定的类加载逻辑,如热部署、网络加载等。自定义类加载器的基本步骤如下:

  1. 继承ClassLoader:创建一个新的类继承自java.lang.ClassLoader。该类有两个核心方法,一个是loadClass方法,其中实现了双亲委派机制,另一个方法是findClass,默认是空方法。
  2. 重写findClass方法:这是自定义类加载器的核心,可用于实现类文件的自定义加载。
  3. 调用defineClass方法:调用父类的defineClass方法,使用字节数据和类名创建Class对象。

以下是一个简单的自定义类加载器示例:

public class MyClassLoader extends ClassLoader {  
  
    private String classPath;  
    
    public MyClassLoader(String classPath) {  
        this.classPath = classPath;  
    }  
  
    @Override  
    protected Class<?> findClass(String fullClassName) throws ClassNotFoundException {  
        // 自定义类文件加载路径
        String filePath = this.classPath + fullClassName.replace(".", "/").concat(".myclass");  
        int code;  
        try {  
            FileInputStream fis = new FileInputStream(filePath);  
            fis.read();  
            ByteArrayOutputStream bos = new ByteArrayOutputStream();  
            try {  
                while ((code = fis.read()) != -1) {  
                    bos.write(code);  
                }  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            byte[] data = bos.toByteArray();  
            bos.close();  
            // 将字节数组转为Class对象
            return defineClass(fullClassName, data, 0, data.length);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
    
    public static void main(String[] args) throws Exception{  
        MyClassLoader myClassLoader = new MyClassLoader("/Users/JVMDemo/target/classes");  
        Class<?> clazz = myClassLoader.loadClass("com.classLoader.SecretUtil");  
        Object obj = clazz.newInstance();  
        Object result = clazz.getMethod("hello", null).invoke(obj);  
        System.out.println(result);  
    }
    
    public class SecretUtil {  
        public String hello(){  
            return "Hello";  
        }  
    }

}
    
运行结果:
    Hello

3.1 设置父ClassLoader

在实例化自定义类加载器前会先初始化继承的ClassLoader,即最终会先调用ClassLoader类的构造方法,将AppClassLoader设置为自定义类加载器的父加载器,意味着自定义ClassLoader调用的还是AppClassLoader的loadClass方法,遵循双亲委派的加载机制。

public abstract class ClassLoader {
    // 持有父加载器的引用
    private final ClassLoader parent;
    
    protected ClassLoader() {  
        // 将默认的系统类加载器,作为父加载器传入,赋值parent
        this(checkCreateClassLoader(), getSystemClassLoader());  
    }
    
    private ClassLoader(Void unused, ClassLoader parent) {  
        this.parent = parent;  
        if (ParallelLoaders.isRegistered(this.getClass())) {  
            parallelLockMap = new ConcurrentHashMap<>();  
            package2certs = new ConcurrentHashMap<>();  
            assertionLock = new Object();  
        } else {  
            parallelLockMap = null;  
            package2certs = new Hashtable<>();  
            assertionLock = this;  
        }  
    }

    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;  
                // 调用Launcher的getClassLoader方法,返回在类加载器初始化时记录的AppClassLoader
                scl = l.getClassLoader();  
                try {  
                    scl = AccessController.doPrivileged(new SystemClassLoaderAction(scl));  
                } catch (PrivilegedActionException pae) {  
                    oops = pae.getCause();  
                    if (oops instanceof InvocationTargetException) {  
                        oops = oops.getCause();  
                    }  
                }  
                if (oops != null) {  
                    if (oops instanceof Error) {  
                        throw (Error) oops;  
                    } else {  
                        // wrap the exception  
                        throw new Error(oops);  
                    }  
                }  
            }  
            sclSet = true;  
        }  
    }
}

4 总结

本文详细介绍了JVM中的类加载器,包括类加载器的类型、初始化过程以及如何通过自定义类加载器来实现特定的类加载需求。通过理解类加载器的工作原理,我们可以更好地掌握JVM的类加载机制,为开发高效、稳定的Java应用程序打下坚实基础。

在接下来的博文中,我们将详细介绍双亲委派模型的工作原理,这是JVM类加载体系的核心设计之一。双亲委派模型确保了Java核心库的稳定性,并避免了类的重复加载,同时我们也将讨论如何通过自定义类加载器来打破这一模型,以及这样做的意义与影响。

Comments

Sort byBest