这里大量参考了如下博客
https://blog.csdn.net/javazejian/article/details/73413292
ClassLoader是一个抽象类,他有很多个实现
BootstrapClassLoader是最顶层的classloader 负责加载jvm需要的类
ExtClassLoader 是次级的classloader 负责加载
AppClassLoader 是我们最常用的。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,本身也是 sun.misc.Launcher$AppClassLoader。
BootstrapClassLoader -》ExtClassLoader -》AppClassLoader 存在这样的父子关系。
但是这三个类之间本身没什么直接继承关系,他们都是ClassLoader的子类(BootstrapClassLoader除外 它由c++实现),但是通过组合模式 实现了类似父子关系的关系。双亲委派模式
双亲委派模式的实现是依赖于 ClassLoader抽象类定义的loadClass() 方法。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 从缓存中尝试获取 如果已经加载过就不再加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//没获取到就尝试从父加载器加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果父为null 说明已经是BootstrapClassLoader了
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 如果父类无法加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 通过findClass 加载 类
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 是否需要解析类
if (resolve) {
resolveClass(c);
}
return c;
}
}
也就是说他们通过非继承关系实现了类似父子关系。
findClass()方法,方法返回Class对象,他是在loadClass父类无法加载时 由自己加载的方法。ClassLoader本身也没有实现findClass方法 我们的extClassLoader 和 appClassLoader 都是URLClassLoader的子类 而 URLClassLoader 重写了findClass方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 替换为相对 路径
String path = name.replace('.', '/').concat(".class");
// 转换成Resource对象 ucp 里 的path属性包含了 能够加载的 类路径
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 转换成真正的class对象
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
extClassLoader 能够加载的目录
appClassLoader能够加载的目录
这也就是为啥静态覆盖类能够生效的原因,因为classes的加载在 外部jar 包之前。
再来看defineClass
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
// 取最后一个点的位置
int i = name.lastIndexOf('.');
// 获取到刚才resource 中的真实url地址
URL url = res.getCodeSourceURL();
if (i != -1) {
// 取到 包名
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
// 检查包名的密封性
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
// 从res 中获取 如果有就用res中的
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
//获取类的byte数组
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
// 真正读取byte数组并 创建class 对象
return defineClass(name, b, 0, b.length, cs);
}
}
再到ClassLoader抽象类中
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
// 检查是否能够进行加载
protectionDomain = preDefineClass(name, protectionDomain);
// 获取 加载class的 加载地址 比如file:/E:/myWorkSpace/interview/target/classes/
String source = defineClassSourceLocation(protectionDomain);
// 加载class native方法
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
//加载 后置方法
postDefineClass(c, protectionDomain);
return c;
}
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
// 包名不能以java开头
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
所以loadClass 是实现双亲委派机制 而findClass才是真正加载类的地方。
ExtClassLoader 和 AppClassLoader 都是Launcher的内部类 他们是在Launcher 的构造方法中创建的
Launcher
public Launcher() {
// 创建extClassLoader
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
// 创建AppClassLoader
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 给线程上下文设置ClassLoader 为 appClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
// 这里设置安全检查 默认为 null 不创建
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
违反双亲委派
1.调用findClass方法
2.继承URLClassLoader 如果重写了 loadClass方法并且 不再实现双亲委派的机制 或者直接调用了 findClass方法
违反双亲委派的用途
1.热部署
2.自定义地址class加载(从非classPath中加载)
3.SPI
包名为 java.开头的类是不允许加载的 这时如果 rt.jar 里的某个接口需要第三方提供实现(比如sql.Driver) 由于rt.jar 是由 bootstrapClassLoader 加载的 但是它又不能加载第三方jar包,这时怎么办 就需要违反双亲委派机制 由 bootstrapClassLoader 委派 appClassLoader来进行加载
//DriverManager是Java核心包rt.jar的类
public class DriverManager {
//省略不必要的代码
static {
loadInitialDrivers();//执行该方法
println("JDBC DriverManager initialized");
}
//loadInitialDrivers方法
private static void loadInitialDrivers() {
sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//加载外部的Driver的实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
// 实际上在这里才加载
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
}
public static <S> ServiceLoader<S> load(Class<S> service) {
// 这里就获取到了我们之前Launcher注册的 线程上下文加载器 也就是appClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 这里创建了一个ServiceLoader 保存了 信息
return ServiceLoader.load(service, cl);
}
// 这里的next java.util.ServiceLoader#iterator#next
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 会进入这里
return lookupIterator.next();
}
// 这里是 java.util.ServiceLoader.LazyIterator #next
public S next() {
if (acc == null) {
//进入这里
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() {
return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
这就实现了委托appClassLoader给bootstrapClassLoader加载类信息 也就是为何提供了违反双亲委派的方法。