Java类加载

JVM的三种加载器

根加载器

  • 加载lang包核心类、rt.jar

扩展加载器

  • 加载java.ext.dir目录下的jar包

系统加载器

  • 加载java.class.path下jar包

机制

除根加载器外,每个类加载器都有父加载器,当加载某个类时首先让父加载器(加载器链路)们尝试加载,如果父加载器们加载失败,则捕获异常并调用自己的findClass方法尝试加载。

依赖顺序:自定义加载器->系统加载器->扩展加载器->根加载器。

用户自定义加载器需要继承抽象类ClassLoad,创建自定义加载器时可以指定父加载器,默认的父加载器为系统加载器,如果指定为空则父加载器为根加载器。

JVM运行过程中遇到了需要加载的类,由当前类的定义类加载器(加载当前类的加载器)来加载。

命名空间

每个加载器都有自己的命名空间,命名空间由该加载器和所有父加载器加载的类构成的,可以理解为同一个类加载系统。同一个命名空间中,只存在一个名称及包名相同的Class实例,同一个命名空间内才允许类之间相互访问。

JVM之所以使用父委托机制来实现类加载,主要出于安全考虑,在同一个加载器系统中不存在同一个类名、包名相同的类,一个同样类名、包名的类不会加载第二次,因此体系中已经被加载的系统类不会被恶意代码篡改。

运行时包

定义:相同包名且由同一加载器加载的一部分类。

如果有在同一加载器系统中包名相同但由不同类加载器加载的类,他们不在同一个运行时包,所以理论上不具有相同包的权限。

运行时包的存在同样是出于安全考虑。

自定义加载器

如何实现自定义加载器?

调用抽象类ClassLoad的loadClass方法时会调用parent的loadClass方法,如果parent为空则调用根加载器的loadClass,如果父加载器方法抛出异常则调用自己的findClass方法,
该方法在ClassLoad类中默认抛出ClassNotDeError,用户需要自己实现相关find class逻辑。ClassLoad中的defineClass方法可以通过class文件的二进制数据初始化为Class对象。

JavaEE的类加载

JavaEE的类加载器默认行为是首先在Bootstrap中加载,如果加载不到则先从Webapp下面加载。JavaEE规范推荐每个模块的类加载器先加载本类加载的内容,如果加载不到才回到parent类加载器中尝试加载。
Tomcat有自定义的类加载器,父加载器为根加载器,加载时会首先加载应用下的lib,然后再加载tomcat下的lib。

JavaEE的 委派模型:
JavaEE的类加载

类加载过程

加载、连接、初始化:
类加载过程

连接阶段:
类加载过程