「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
书接上文的类加载模式,本节我们看下类加载双亲委派
什么是双亲委派
对于.class字节码文件,我们的JVM虚拟机需要通过类加载器来加载他们,但是类加载器众多,我们无法确定该由哪个类来进行加载!所以java首先加载的时候不会直接加载该类,而是会把这个任务委托给他的上级类加载器加载,重复这个操作,只有上级无法加载该.class文件时,自己才会去加载这个.class。
类加载器类型
**Bootstrap ClassLoader(启动类加载器),**C++实现,加载Java基础类%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等
**Extension ClassLoader(标准扩展类加载器)**加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
Application ClassLoader (应用类加载器) 加载当前应用的classpath下的所有jar和类
**用户自定义类加载器 ** 加载指定路径下的class文件
以上的只是针对1.8及之前的jdk版本,在1.9之后引入的模块化的概念,以前的基础jar包会被拆分成很多的模块,编译的时候只编译实际用到的模块,各个类加载器各司其职,只加载自己负责的模块。
为什么要这样一层一层去请求呢?因为通过这种方式可以避免类的重复加载,保证了代码的安全性,不至于别人恶意破坏基础库或者某些代码的冲突;
实现该功能的主要方法是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 {
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();
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;
}
}
具体意思我们直接看api的说明
加载具有指定二进制名称的类。 此方法的默认实现按以下顺序搜索类:
调用findLoadedClass(String)来检查类是否已经加载。
在父类加载器上调用loadClass方法。 如果 parent 为null ,则使用虚拟机内置的类加载器。
调用findClass(String)方法来查找类。
如果使用上述步骤找到了该类,并且解析标志为真,则此方法将在生成的Class对象上调用resolveClass(Class)方法。
鼓励ClassLoader 的子类覆盖findClass(String) ,而不是这个方法。
除非被覆盖,否则在整个类加载过程中,此方法会同步getClassLoadingLock方法的结果
注意此处的同步代码块;
ClassLoader类默认注册为具有并行能力。
但是,如果它们具有并行能力,它的子类仍然需要注册自己。
在委托模型不是严格分层的环境中,类加载器需要具有并行能力,
否则类加载会导致死锁,因为加载器锁在类加载过程中一直保持着
在看下上面源码的findclass方法,没有方法体,说明如果没有找到的话,会调用我们的自定义类加载器;这段内容得我们自己写;
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
既然说到这里了,我们怎么写自定义加载器呢
手写一个自定义类加载器
- 继承ClassLoader
- 重写findclass方法
我们这也重写了loadclass方法,可是没有调用重写的loadclass去加载,loadclass仅仅是获取二进制文件名(网上不建议重写该方法,我问公司大佬,他给的解释是自定义的动态加载方法是隔离状态的,类似于Tomcat的pandora(潘多拉),与项目本身不会冲突),类加载工作是通过_defineClass_来实现的所以最后我们还得调用该方法,_defineClass_主要签名该类,底层调用下面这个方法;
在我们日常开发中,这个遇到的最多的场景就是热加载,
打破的双亲委派
对于Tomcat,就打破了双亲委派,它是以沙箱的形式通过隔离来实现的;每个实例只会加载自己实例里的文件,JDBC也打破了双亲委派,关于jdbc的打破,查的资料是由于DriverManager.getconnection()会被根加载器加载,但是呢,但是里面的驱动比如说MYSQL、ORACLE的都是第三方的,而根加载器无法加载,所以引入下面代码的一段线程的上下文类加载器
@CallerSensitive
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
好啦,好啦,说的不是很深,如果感兴趣,可以自行再探索探索
觉得还行,点个赞再走嘛,交个朋友嘛