Java 类加载器(Class Loader)是 Java 虚拟机(JVM)的一部分,负责将类的字节码加载到内存中,并将其转换为可执行的 Java 对象。类加载器在 Java 应用程序中起着重要的作用,它实现了动态加载类的机制,使得 Java 具备了灵活性和可扩展性。
类的生命周期通常包括:加载、链接(验证、准备、解析)、初始化、使用和卸载。其中类加载的三个阶段为:加载、链接(验证、准备、解析)、初始化、
其作用分别为:
Java 类加载器:
Java 虚拟机用于加载类文件的一种机制。在 Java 中,每个类都由类加载器加载,并在运行时被创建为一个 Class 对象。类加载器负责从文件系统、网络或其他来源中加载类的字节码,并将其转换为可执行的 Java 对象。类加载器还负责解析类的依赖关系,即加载所需的其他类。
虚拟机内部提供了三种类加载器:
用户可以自定义类加载器。
类加载器采用了双亲委派模型(Parent Delegation Model)来加载类。即当一个类加载器需要加载类时,它会首先委派给其父类加载器加载。如果父类加载器无法加载,才由该类加载器自己去加载。这种层级关系使得类加载器能够实现类的共享和隔离,提高了代码的安全性和可靠性。
双亲委派模型的执行流程:
1、当加载一个类时,会先从应用程序类加载器的缓存里查找相应的类,如果能找到就返回对象,如果找不到就执行下面流程;
2、在扩展加载器缓存中查找相应的类,如果能找到就返回对象,如果找不到就继续下面流程;
3、在启动类加载器中查询相应的类,如果找到就返回对象,如果找不到就继续下面流程;
4、在扩展加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就继续下面流程;
5、在应用程序类加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就返回 ClassNotFound 异常。
即当一个类加载器收到了一个类加载请求时,它自己不会先去尝试加载这个类,而是把这个请求转交给父类加载器
。
双亲的含义:
ClassLoader 内的 loadClass 方法中的双亲委派实现:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//检查该class是否已经被当前类加载器加载过,这是一个抽象方法,具体实现由子类实现
Class<?> c = findLoadedClass(name);
if (c == null) {
//此时该class还没有被加载
try {
if (parent != null) {
//如果父加载器不为null,则委托给父类加载
c = parent.loadClass(name, false);
} else {
//如果父加载器为null,说明当前类加载器已经是启动类加载器,直接时候用启动类加载器去加载该class
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
//此时父类加载器都无法加载该class,则使用当前类加载器进行加载
long t1 = System.nanoTime();
c = findClass(name);
...
}
}
//是否需要连接该类
if (resolve) {
resolveClass(c);
}
return c;
}
}
安全
用户无法伪造不安全的系统类。根据双亲委派模型,jre 提供的类在启动类和扩展类加载时加载早于用户伪造的系统类的应用类加载。
避免重复加载
当一个类加载后,会被缓存,不会出现多个类加载器将同一个类重复加载的情况。
加载 SPI 实现类的场景
类加载的范围受到限制,某些情况下父 class loader 无法加载某些类文件,这时候就需要委托到下层级的 class loader 去加载类文件。
双亲委派使得启动类加载器无法加载用户的 jar 包,比如:
参考 ClassLoader 的实现流程,需要依次实现:
在 ClassLoader 的源码中,提供了一个自定义类加载器的模版:
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
// 读取class文件,转化为字节数组
byte[] b = loadClassData(name);
// 读取字节数组,转化为Class对象
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
}
}
可以看到只需要继承 ClassLoader,并且重写 findClass 方法即可。
jdk 为了统一管理数据库驱动,在 java.sql 下定义了 Driver 接口,具体的实现由数据库厂商去做。
// 使用应用类加载器加载 Driver 实现类
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
//将mysql的Driver注册进驱动管理器中
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
加载 Driver 实现类的过程:
没有破坏双亲委派
// 直接调用 DriverManager获取驱动列表
Enumeration<Driver> en = DriverManager.getDrivers();
while (en.hasMoreElements()) {
java.sql.Driver driver = en.nextElement();
System.out.println(driver);
}
应用类加载器逐层委托到启动类加载器去加载 DriverManager 时,会同时执行它的静态方法
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
启动类加载 DriverManager,之后需要通过 spi 机制去加载 jar 包中的 Driver 类,而该 Driver 理应被应用类加载器加载,这个时候就需要启动类加载器去通知应用类加载器,这明显违背了双亲委派机制。
loadInitialDrivers 方法:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
ServiceLoader.load 方法,Thread.currentThread().getContextClassLoader()是线程上下文类加载器,使用的是线程上下文类加载器去加载的 Driver 实现类。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
在 sun.misc.Launcher 类中,将应用类加载器设置进了线程上下文类加载器中,通过线程上下文类加载器,我们可以拿到应用类加载器的引用:
public Launcher() {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
Thread.currentThread().setContextClassLoader(this.loader);
}
打破双亲委派的过程:
在 jdbc4.0 的情况下,梳理一下整个过程:
TOMCAT 的两个基础功能点:
JVM 确定类的唯一性:由类加载器实例+全限定名一起确定的。全限定名相同,类加载器不同,则会被认定为不同的类。
应用 A 和应用 B 所依赖的 Spring 版本不同,却依旧可以运行。
webapps 下的每一个应用都会对应一个不同的类加载器实例,用以保持应用间的隔离。
某个自定义的类加载想要打破双亲委派,只需要重写 loadClass 方法即可。
Tomcat 中的 WebappClassLoader 就是自定义类加载器,它的 loadClass 方法为:
public Class loadClass(String name) throws ClassNotFoundException {
return (loadClass(name, false));
}
public Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class clazz = null;
// Log access to stopped classloader
if (!started) {
try {
throw new IllegalStateException();
} catch (IllegalStateException e) {
log.info(sm.getString("webappClassLoader.stopped", name), e);
}
}
//1、从自己的本地缓存中查找,本地缓存的数据结构为ResourceEntry
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
//2、从jvm的缓存中查找
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
//3、如果缓存中都找不到,则利用系统类加载器加载
try {
clazz = system.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name);
//4、开启代理的话,则使用父加载器加载
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
}
//5、自行加载
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
//如果自己也加载不了,那就只能让父加载器加载了
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
}
throw new ClassNotFoundException(name);
}
内部逻辑:
即: