由源码深入Java类加载器(双亲委派模型)
JVM类加载器
JVM主要有以下几种类加载器:
- 引导类加载器
主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类。 - 扩展类加载器
主要加载JVM中扩展类,位于JRE的ext目录下。 - 应用程序类加载器
主要负责加载ClassPath路径下的类,也就是业务类。 - 自定义加载器
负责加载用户自定义路径下的类。
类加载器关系
源码解析
ExtClassLoader和AppClassLoader的创建流程
先看下Launcher的构造方法:
public Launcher() {Launcher.ExtClassLoader var1;try {//获取扩展类加载器var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {//获取应用类加载器,this.loader就是默认的类加载器:即AppClassLoaderthis.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}//设置默认classLoaderThread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");}
ExtClassLoader
看下ExtClassLoader的获取方法getExtClassloader():
可以看到ExtClassLoader是Launcher的一个内部类,继承的是URLClassLoader。
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {//获取要加载的类文件final File[] var0 = getExtDirs();try {return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {public Launcher.ExtClassLoader run() throws IOException {int var1 = var0.length;for(int var2 = 0; var2 < var1; ++var2) {MetaIndex.registerDirectory(var0[var2]);}//new一个ExtClassLoaderreturn new Launcher.ExtClassLoader(var0);}});} catch (PrivilegedActionException var2) {throw (IOException)var2.getException();}}
查看getExtDirs()方法:可以看到要加载的类文件都是位于ext文件夹下的。
private static File[] getExtDirs() {String var0 = System.getProperty("java.ext.dirs");File[] var1;if (var0 != null) {StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);int var3 = var2.countTokens();var1 = new File[var3];for(int var4 = 0; var4 < var3; ++var4) {var1[var4] = new File(var2.nextToken());}} else {var1 = new File[0];}return var1;}
继续看ExtClassLoader的构造方法:
public ExtClassLoader(File[] var1) throws IOException {super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);}
调用了父类的构造方法:
可以看到ExtClassLoader的parent赋值为null,因为引导类加载器是C++语言写的,没有实际java对象。
public URLClassLoader(URL[] urls, ClassLoader parent,URLStreamHandlerFactory factory) {super(parent);// this is to make the stack depth consistent with 1.1SecurityManager security = System.getSecurityManager();if (security != null) {security.checkCreateClassLoader();}acc = AccessController.getContext();ucp = new URLClassPath(urls, factory, acc);}
这样一个ExtClassLoader就创建好了。
AppClassLoader
AppClassLoader同样也是继承了URLClassLoader类
看下getAppClassLoader方法:
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {final String var1 = System.getProperty("java.class.path");final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {public Launcher.AppClassLoader run() {URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);return new Launcher.AppClassLoader(var1x, var0);}});}
可以看到,getAppClassLoader主要加载工程classPath下的类文件。
继续看getAppClassLoader构造方法:
AppClassLoader(URL[] var1, ClassLoader var2) {super(var1, var2, Launcher.factory);this.ucp.initLookupCache(this);}
从一开始的Launcher构造方法中可以看到参数var2就是先初始化的extClassLoader。
同样调用了父类URLClassLoader的构造,将extClassLoader设置为parent,所以appClassLoader的parent是extClassLoader。
由此三个主要类加载器之间的关系弄清楚了,各自要加载的范围也弄清楚。我们再看看自定义类加载器的实现。
自定义类加载器
自定义类加载器要继承ClassLoader方法,只需要重写findClass方法就行了:
package classload;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/*** @author zhw* @description* @date 2021-07-15 14:36*/
public class MyClassLoader extends ClassLoader{@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");try{byte[] bytes = getClassBytes(file);//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.ClassClass<?> c = defineClass(name, bytes, 0, bytes.length);return c;} catch (Exception e) {e.printStackTrace();}return super.findClass(name);}private byte[] getClassBytes(File file) throws Exception{FileInputStream inputStream = new FileInputStream(file);//原始输入流ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) != -1 ) {baos.write(buffer, 0, len);}baos.flush();return baos.toByteArray();}
}
关于自定义类加载器的parent是谁,可以查看:
protected ClassLoader() {this(checkCreateClassLoader(), getSystemClassLoader());}
继续看getSystemClassLoader():
public static ClassLoader getSystemClassLoader() {initSystemClassLoader();if (scl == null) {return null;}SecurityManager sm = System.getSecurityManager();if (sm != null) {checkClassLoaderPermission(scl, Reflection.getCallerClass());}return scl;}private static synchronized void initSystemClassLoader() {if (!sclSet) {if (scl != null)throw new IllegalStateException("recursive invocation");sun.misc.Launcher l = sun.misc.Launcher.getLauncher();if (l != null) {Throwable oops = null;scl = l.getClassLoader();}sclSet = true;}}public ClassLoader getClassLoader() {return this.loader;}
返回的是this.loader。上面已经知道loader就是AppClassLoader。所以自定义类加载器的默认parent就是AppClassLoader。
双亲委派
在类加载流程中,首先调用的是Launcher.loader.loadClass()方法。
public Launcher() {Launcher.ExtClassLoader var1;try {//获取扩展类加载器var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {//获取应用类加载器,this.loader就是默认的类加载器:即AppClassLoaderthis.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}//设置默认classLoaderThread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");}
loader就是AppClassLoader。所以继续看AppClassLoader.loadClass方法:
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {int var3 = var1.lastIndexOf(46);if (var3 != -1) {SecurityManager var4 = System.getSecurityManager();if (var4 != null) {var4.checkPackageAccess(var1.substring(0, var3));}}if (this.ucp.knownToNotExist(var1)) {Class var5 = this.findLoadedClass(var1);if (var5 != null) {if (var2) {this.resolveClass(var5);}return var5;} else {throw new ClassNotFoundException(var1);}} else {//调用父类的loadClass方法return super.loadClass(var1, var2);}}
继续看super.loadClass(var1, var2):双亲委派机制的核心代码来了
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 {//如果父加载器不为null,则交给父加载器加载。if (parent != null) {c = parent.loadClass(name, false);} else { //如果父加载器为null,则交给引导类加载器加载。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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}
看完上面的代码后,是不是觉得双亲委派机制的实现很简单?
双亲委派的作用:
- 沙箱安全,保证JVM核心代码不被用户自定义类覆盖。
- 保证了类加载的唯一性。
如何打破双亲委派?
看双亲委派机制的源码,可以看到主要实现实在loadClass方法中,那么,只需要重写loadClass(String name, boolean resolve)方法即可:
package classload;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/*** @author zhw* @description* @date 2021-07-15 14:36*/
public class MyClassLoader extends ClassLoader{@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");try{byte[] bytes = getClassBytes(file);//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.ClassClass<?> c = defineClass(name, bytes, 0, bytes.length);return c;} catch (Exception e) {e.printStackTrace();}return super.findClass(name);}protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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("hiwei.test.Person".equals(name)){c = findClass(name);}else{c = getParent().loadClass(name);}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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}private byte[] getClassBytes(File file) throws Exception{FileInputStream inputStream = new FileInputStream(file);//原始输入流ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) != -1 ) {baos.write(buffer, 0, len);}baos.flush();return baos.toByteArray();}
}
测试类:
package classload;
/*** @author zhw* @description* @date 2021-07-15 15:09*/
public class ClassLoadTest {public static void main(String[] args) throws Exception {MyClassLoader myClassLoader = new MyClassLoader();Class<?> clazz = Class.forName("hiwei.test.Person", true, myClassLoader);Object o = clazz.newInstance();System.out.println(o.toString());System.out.println(clazz.getClassLoader());}
}
测试:
目标文件夹和classPath都存在Person.class
- 测试一:
结果:使用自定义加载器加载。
- 测试二:不覆盖loadClass方法。
结果:使用AppClassLoader
破坏双亲委派的应用
tomcat破环双亲委派
在tomcat中不同的应用可能依赖同一个jar的不同版本,如果共用一个类加载器,会导致无法进行环境隔离。所以tomcat自定义类加载器,每个应用都有自己的类加载器,负责加载自己应用下的类,打破了双亲委派机制,不在让父加载器先加载。
源码分析
tomcat的Bootstrap.initClassLoaders()方法中会初始化tomcat核心类的类加载器:
private void initClassLoaders() {try {commonLoader = createClassLoader("common", null);if( commonLoader == null ) {// no config file, default to this loader - we might be in a 'single' env.commonLoader=this.getClass().getClassLoader();}catalinaLoader = createClassLoader("server", commonLoader);sharedLoader = createClassLoader("shared", commonLoader);} catch (Throwable t) {handleThrowable(t);log.error("Class loader creation threw exception", t);System.exit(1);}}
这三个类加载器并未破坏双亲委派模型,这三个都是URLClassLoader的实例。
真正破坏双亲委派模型的是WebappClassLoader类加载器,WebappClassLoader继承了WebappClassLoaderBase,而WebappClassLoaderBase重写了loadClass方法:
@Override//todo 此处破坏了双亲委派模型public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {if (log.isDebugEnabled())log.debug("loadClass(" + name + ", " + resolve + ")");Class<?> clazz = null;// Log access to stopped class loadercheckStateForClassLoading(name);// (0) Check our previously loaded local class cacheclazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled())log.debug(" Returning class from cache");if (resolve)resolveClass(clazz);return clazz;}//省略,,,,}
可以看到,重写的loadClass方法破坏了双亲委派模型。
JDBC破坏双亲委派
原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的jar中的Driver类具体实现的。
以以下版本为例:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version>
</dependency>
Driver实现类:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}
可以看到,使用了DriverManager类。在DriverManager类中有静态代码块:
static {loadInitialDrivers();println("JDBC DriverManager initialized");}
继续看loadInitialDrivers()
private static void loadInitialDrivers() {String drivers;try {drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {return System.getProperty("jdbc.drivers");}});} catch (Exception ex) {drivers = null;}AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {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;}});println("DriverManager.initialize: jdbc.drivers = " + drivers);if (drivers == null || drivers.equals("")) {return;}String[] driversList = drivers.split(":");println("number of Drivers:" + driversList.length);for (String aDriver : driversList) {try {println("DriverManager.Initialize: loading " + aDriver);Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println("DriverManager.Initialize: load failed: " + ex);}}}
看下面方法:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}
使用了当前线程的classLoader。
private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}
回到loadInitialDrivers()方法,继续往下看:
AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);//加载Driver.classIterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});
进入loadedDrivers.iterator():
public Iterator<S> iterator() {return new Iterator<S>() {Iterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();public boolean hasNext() {if (knownProviders.hasNext())return true;return lookupIterator.hasNext();}public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}};}
可以看到返回了一个重写了hasNext()和next()方法的匿名Iterator类。
try{while(driversIterator.hasNext()) {driversIterator.next();}}
在这里调用的都是重写方法。
由调用关系,最终可以看到下面的方法:
private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)//找到Driver.calssconfigs = ClassLoader.getSystemResources(fullName);elseconfigs = loader.getResources(fullName);} catch (IOException x) {fail(service, "Error locating configuration files", x);}}while ((pending == null) || !pending.hasNext()) {if (!configs.hasMoreElements()) {return false;}pending = parse(service, configs.nextElement());}nextName = pending.next();return true;}private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {//加载Driver.calssc = 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}
可以看到,Driver.class是在hasNextService()中取到,nextService()中加载的:
c = Class.forName(cn, false, loader);
这里的类加载器loader就是上面的
ClassLoader cl = Thread.currentThread().getContextClassLoader();
现在真相大白了,在使用spi机制时,会使用当前线程的类加载器加载"META-INF/services/"下面的Driver.class。
在双亲委派模型下,类的加载是由下至上委托的,jdk无法加载其它文件夹下的类文件。但是在jdbc中,Driver要由供应商实现,所以需要进行加载,在spi使用方法中,使用线程上下文类加载器加载指定路径下的Driver.class文件,解决了这个问题。
JDBC破坏双亲委派的实现是使用父加载器加载指定路径下的class文件。
由源码深入Java类加载器(双亲委派模型)相关推荐
- 80070583类不存在_结合JVM源码谈Java类加载器
一.前言 之前文章 加多:ClassLoader解惑zhuanlan.zhihu.com 从Java层面讲解了Java类加载器的原理,这里我们结合JVM源码在稍微深入讲解下. 二.Java类加载器的 ...
- java类加载和双亲委派模型浅说
本文目录 前言 一.类加载器 1.1 类加载机制的基本特征 1.2 类加载的分类 1.3 类加载器 A.启动类加载器(引导类加载器,Bootstrap ClassLoader) B.扩展类加载器(Ex ...
- java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...
Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下我们先从启动类说起有一个Launcher类 ...
- JVM源码分析--ClassLoader类加载器
本人原创,转载请注明出处:https://www.cnblogs.com/javallh/p/10224187.html 1.JDK已有类加载器: BootStrap ClassLoader (启动类 ...
- 类加载器双亲委派模式
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只 ...
- 类加载器-双亲委派机制
上一篇:类加载器-分类 一.概述 除了根类加载器之外,其他的类加载器都需要有自己的父加载器.从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全.除了虚拟机自带的 ...
- Java类加载机制双亲委派机制
关键知识点提炼: 类的唯一性:类的实例= 类加载器 ➕全限定类名 (扩展pandora容器隔离原理-类加载器隔离) 类加载过程:家(加)宴(验)准备了西(析)式菜. 加载-验证-准备-解析-初始化 双 ...
- 面向对象回顾(静态变量、类加载机制/双亲委派模型、Object类的方法、类和对象区别)
1. 静态变量存在什么位置? 方法区 2. 类加载机制,双亲委派模型,好处是什么? 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务, ...
- java 打破双亲委派,为什么说java spi破坏双亲委派模型?
虽然有SPI破坏双亲委派模型的说法,但我不太认同.简单说下. 双亲委派模型(再次吐槽下这个翻译),是一种加载类的约定.这个约定的一个用处是保证安全.比如说你写Java用了String类,你怎么保证你用 ...
最新文章
- 【原创】源智工作流聚合步骤模型
- android或java timer声明
- inline-block 和 float 的区别
- Adaptive Images : 为不同的屏幕尺寸提供不同的图片
- 一文读懂python本地开发环境配置
- libstdc和glibc的一些共享库问题
- 决策树:ID3和C4.5
- 【181023】有意思的屏幕画笔,基于VC++实现
- muduo网络库:05---线程同步精要之(线程安全的Singleton实现)
- Java(1):Java SE疯狂复习基本数据类型、OOP
- MAC系统下解决Teamviewer 被误认为商业用途的问题
- 鸿合一体机触屏没反应怎么办_电脑一体机触摸屏没反应 触摸屏电脑一体机常见故障解决方法...
- 一个企业如何运营微商管理系统?
- Spring IOC详解 以及 Bean生命周期详细过程 可以硬刚面试官的文章
- ERROR 1819 (HY000) Your password does not satisfy the current policy requirements
- 微信小程序上传单张或多张图片
- 维克森林大学计算机科学专业好不好,维克森林大学商业分析硕士怎么样?
- 用strcmp来打印输入三次密码哔哩练习
- 成功解决Component template should contain exactly one root element
- Revit:Revit无法正常运行外部程序“tangentUIApp”解决办法
热门文章
- php单元格,PHP中的单元格怎么利用PhpSpreadsheet进行设置
- uniapp上传图片和视频到OSS
- 【WPS表格】条件格式功能的部分运用
- Linux怎么强制关闭docker 关闭某个进程 查看某个进程 关闭程序 关闭进程
- Teebik:2017H1墨西哥畅销榜:超级马里奥入围十强 ARPG市场潜力巨大
- 钻石的基本知识(买钻戒的时候需要了解)
- ICLOUD储存空间要升级吗_白茶的储存,既要讲究密封,又要保持通风,是开玩笑吗?...
- 001 打印机加墨后老是弹出窗口
- yii2 php init,Yii2 使用 .env 来配置项目环境变量
- 51单总线控制SV-5W语音播报模块