欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > Java核心: 类加载器

Java核心: 类加载器

2024/10/24 8:19:27 来源:https://blog.csdn.net/randavy/article/details/139567554  浏览:    关键词:Java核心: 类加载器

这一节我们来学习Java的类加载器,以及常用的类加载器实现URLClassLoader。

1. Java类加载器

类加载器用于将字节码读取并创建Class对象。我们知道JVM本身是用C写的,一开始执行的时候由C程序来加载并引导字节码的运行,这些由C编写的加载字节码的类加载器被称为BootstrapClassLoader。BootstrapClassLoader负责加载Java的运行时(${JAVA_HOME}/jre/lib),比如rt.jar、resources.jar。 Java运行时实现了自己的类加载器PlatformClassLoader,负责加载扩展类库(${JAVA_HOME}/jre/lib/ext)。 此外Java运行时还提供了AppClassLoader用于加载CLASSPATH下指定的jar。

类加载器

说明

加载的范围

BootstrapClassLoader

根类加载器,加载JRE核心,C/C++实现

JAVA_HOME/jre/lib

PlatformClassLoader

平台类加载器,加载JRE的扩展类

JAVA_HOME/jre/lib/ext

AppClassLoader

应用类加载器,加载应用类

ClassPath的目录或jar文件

1. 加载过程

类的加载过程被拆分为3步: 装载、链接、初始化,链接本身由被拆为三步: 验证、准备、解析,我们来看看每一步,具体完成了什么操作

  1. 装载,读取字节码(.class文件),创建Class对象
  2. 链接
    1. 验证,校验类文件结构,检查字节码是否合法,是否符合访问权限
    2. 准备,为类静态变量分配空间,设置默认初始值
    3. 解析,将符号引用解析为直接引用,包括类、方法、字段等,指向内存地址
  3. 初始化
    1. 字段的静态初始化,赋予初始化值
    2. 静态初始化块执行

访问类的静态方法和字段、通过反射调用类、创建类的实例都会触发类的初始化,子类被初始化时也会触发父类的初始化。不过有一些特殊的场景,不会执行类的初始化,包括:

  1. 访问静态常量(static final),这类常量被编译优化内联到代码里,因此不需要初始化
  2. 通过子类引用父类的静态字段,不会触发子类的静态初始化
  3. 定义类的数组引用但不赋值,不会触发类的初始化
  4. ClassLoader.load传入参数resolve=false,不会触发链接和初始化
2. 双亲委派

如果我在ClassPath里放一个java.lang.String的类实现,用它替换Java的内置实现的话,风险还是很大的,毕竟用户输入的账号密码都会表示为String对象。双亲委派策略就是用来避免这个问题的。

所谓的双亲委派是指我们用AppClassLoader装载一个类时,会先委托父加载器来加载, 父加载器又会委托它的父加载器加载,只有父加载器无法完成加载的时候,才会尝试自己加载。当加载String类时,会先委托给BootstrapClassLoader,即使用JAVA_HOME/jre/lib中的实现,即使在ClassPath中有java.lang.String的实现,也没有机会被加载。

从代码实现上看也很简单,加载类时先调用父加载器的方法(parent.loadClassOrNull),只有父加载器找不到时,才从自己的ClassPath上查找。详见Java17内置代码: BuiltinClassLoader.loadClass

protected Class<?> loadClassOrNull(String cn, boolean resolve) {synchronized (getClassLoadingLock(cn)) {                              // 加锁,避免同一个类被并发加载// check if already loadedClass<?> c = findLoadedClass(cn);                                 // 如果已加载,直接返回已加载的Class对象if (c == null) {...if (parent != null) {                                         // 如果有父加载器c = parent.loadClassOrNull(cn);                           // 尝试用父加载器加载}...if (c == null && hasClassPath() && VM.isModuleSystemInited()) {c = findClassOnClassPathOrNull(cn);                       // 从ClassPath查找}}if (resolve && c != null)resolveClass(c);                                              // 链接return c;}
}private Class<?> findClassOnClassPathOrNull(String cn) {String path = cn.replace('.', '/').concat(".class");                  // com.keyniu.Main -> com/keyniu/Main.classResource res = ucp.getResource(path, false);                          // 使用URLClassPath读取.class文件if (res != null) {try {return defineClass(cn, res);                                  // 读取Resource装载成Class对象} catch (IOException ioe) {// TBD on how I/O errors should be propagated}}return null;
}
3. URLClassPath

BultinClassLoader.findClassOnClassPathOrNull(String cn)实际是通过URLClassPath.getResource读取.class文件的。URLClassPath相关的核心类如下图所示

  1. URLClassPath,内部封装了查找路径(比如我们常说的ClassPath),URLStreamHandler用于支持不同的Schema读取(如file://、jar://),Loader封装了资源查找的逻辑
  2. URLStreamHandler,支持自定义不同Schame的资源读取,比如file://、jar://
  3. URLStreamHandlerFactory,抽象工厂模块,能整套的替换URLStreamHandler实现
  4. Loader/JarLoader,负责指定Schema指定文件夹/jar文件下读取指定资源

如果我们想要通过URLClassPath加载资源,主要分为3个用例,分别是创建URLClassPath、查找资源、读取资源

  1. 创建URLClassPath,通过构造函数、addFile、addURL将要搜索的根路径传递给URLClassPath
  2. 查找资源findResource(String name, boolean check)方法提供支持,在第1步设置的根路径基础上搜索
    1. 根据根路径的差异,选择Loader、JarLoader、FileLoader等实现
    2. 调用Loader.findResource,查找根路径下是否有对于文件
    3. 返回第一个非空Resource对象,内部包装了URL对象
  3. 由ClassLoader读取第2个用例返回的Resource对象,调用defineClass创建Class对象

我们贴一下整个流程中的核心代码(Java17),为方便阅读由做删减,只保留我们关心的流程。首先看的是URLClassPath根据URL获取Loader实例的逻辑,根据URL的协议和路径生成

  1. 如果是文件(路径不以"/"结尾),直接返回JarLoader
  2. 否则,根据协议返回FileLoader、JarLoader,都不是的话返回Loader
// URLClassPath.getLoader
private Loader getLoader(final URL url) throws IOException {String protocol = url.getProtocol();  // lower cased in URLString file = url.getFile();if (file != null && file.endsWith("/")) {if ("file".equals(protocol)) {return new FileLoader(url);} else if ("jar".equals(protocol) && isDefaultJarHandler(url) && file.endsWith("!/")) {URL nestedUrl = new URL(file.substring(0, file.length() - 2));return new JarLoader(nestedUrl, jarHandler, lmap, acc);} else {return new Loader(url);}} else {return new JarLoader(url, jarHandler, lmap, acc);}
}

使用URL.openConnection时会使用URLStreamHandler,而默认URLStreamHandler是通过DefaultFactory.createStreamHanlder实现的。

// URL.openConnection
public URLConnection openConnection() throws java.io.IOException {return handler.openConnection(this);
}// URL.getURLStreamHandler
static URLStreamHandler getURLStreamHandler(String protocol) {...if (handler == null) {// Try the built-in protocol handlerhandler = defaultFactory.createURLStreamHandler(protocol);}...return handler;
}// DefaultFactory.createURLStreamHandler
private static class DefaultFactory implements URLStreamHandlerFactory {private static String PREFIX = "sun.net.www.protocol.";public URLStreamHandler createURLStreamHandler(String protocol) {// Avoid using reflection during bootstrapswitch (protocol) {case "file":return new sun.net.www.protocol.file.Handler();case "jar":return new sun.net.www.protocol.jar.Handler();case "jrt":return new sun.net.www.protocol.jrt.Handler();}String name = PREFIX + protocol + ".Handler";Object o = Class.forName(name).getDeclaredConstructor().newInstance();return (URLStreamHandler)o;}
}

2. URLClassLoader

讲了这么多,我们来看看一个实例,Java内置的ClassLoader实现: URLClassLoader。URLClassLoader的核心功能是两个

  1. findClass(final String name),加载一个类
  2. findResource(String name),读取一个资源文件
1. 源码阅读

加载资源和类的实现,从代码上看还是很简单,直接调用URLClassPath ucp来加载资源,如果是类加载的话读取资源并调用defineClass创建类

public URL findResource(final String name) {return ucp.findResource(name, true);
}protected Class<?> findClass(final String name) throws ClassNotFoundException {String path = name.replace('.', '/').concat(".class");Resource res = ucp.getResource(path, false);return defineClass(name, res);}
2. 使用示例

我们用URLClassLoader来加载一个SpringBoot应用的jar来做个测试,这个jar解压后的结构看起来是这样的

我们来看一下URLClassLoader读取这个jar的代码

private static void testURLClassLoader(URL url) throws ClassNotFoundException {URLClassLoader ucl = new URLClassLoader("MyURLClassLoader", new URL[]{url}, null);Class<?> clazz = ucl.loadClass("org.springframework.boot.loader.JarLauncher");             // 标准jar结构中的类自动加载System.out.println(clazz);URL is = ucl.getResource("META-INF/MANIFEST.MF");                                          // META-INF下自动加载System.out.println(is);is = ucl.getResource("BOOT-INF/layers.idx");                                               // BOOT-INF下自动加载System.out.println(is);is = ucl.getResource("org/springframework/boot/loader/launch/JarLauncher.class");          // 标准jar结构的.class也能加载System.out.println(is);clazz = ucl.loadClass("com.keyniu.yangsi.YangsiApplication");                              // URLClassLoader不会加载BOOT-INF/classes、BOOT-INF/lib下的类
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com