JVM类加载器

JVM主要有以下几种类加载器:

  1. 引导类加载器
    主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类。
  2. 扩展类加载器
    主要加载JVM中扩展类,位于JRE的ext目录下。
  3. 应用程序类加载器
    主要负责加载ClassPath路径下的类,也就是业务类。
  4. 自定义加载器
    负责加载用户自定义路径下的类。

类加载器关系

源码解析

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;}}

看完上面的代码后,是不是觉得双亲委派机制的实现很简单?

双亲委派的作用:

  1. 沙箱安全,保证JVM核心代码不被用户自定义类覆盖。
  2. 保证了类加载的唯一性。

如何打破双亲委派?

看双亲委派机制的源码,可以看到主要实现实在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

  1. 测试一:
    结果:使用自定义加载器加载。
  2. 测试二:不覆盖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类加载器(双亲委派模型)相关推荐

  1. 80070583类不存在_结合JVM源码谈Java类加载器

    一.前言 之前文章 加多:ClassLoader解惑​zhuanlan.zhihu.com 从Java层面讲解了Java类加载器的原理,这里我们结合JVM源码在稍微深入讲解下. 二.Java类加载器的 ...

  2. java类加载和双亲委派模型浅说

    本文目录 前言 一.类加载器 1.1 类加载机制的基本特征 1.2 类加载的分类 1.3 类加载器 A.启动类加载器(引导类加载器,Bootstrap ClassLoader) B.扩展类加载器(Ex ...

  3. java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...

    Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下我们先从启动类说起有一个Launcher类 ...

  4. JVM源码分析--ClassLoader类加载器

    本人原创,转载请注明出处:https://www.cnblogs.com/javallh/p/10224187.html 1.JDK已有类加载器: BootStrap ClassLoader (启动类 ...

  5. 类加载器双亲委派模式

    双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只 ...

  6. 类加载器-双亲委派机制

    上一篇:类加载器-分类 一.概述 除了根类加载器之外,其他的类加载器都需要有自己的父加载器.从JDK1.2开始,类的加载过程采用双亲委派机制,这种机制能够很好的保护java程序的安全.除了虚拟机自带的 ...

  7. Java类加载机制双亲委派机制

    关键知识点提炼: 类的唯一性:类的实例= 类加载器 ➕全限定类名 (扩展pandora容器隔离原理-类加载器隔离) 类加载过程:家(加)宴(验)准备了西(析)式菜. 加载-验证-准备-解析-初始化 双 ...

  8. 面向对象回顾(静态变量、类加载机制/双亲委派模型、Object类的方法、类和对象区别)

    1. 静态变量存在什么位置? 方法区 2. 类加载机制,双亲委派模型,好处是什么? 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务, ...

  9. java 打破双亲委派,为什么说java spi破坏双亲委派模型?

    虽然有SPI破坏双亲委派模型的说法,但我不太认同.简单说下. 双亲委派模型(再次吐槽下这个翻译),是一种加载类的约定.这个约定的一个用处是保证安全.比如说你写Java用了String类,你怎么保证你用 ...

最新文章

  1. 【原创】源智工作流聚合步骤模型
  2. android或java timer声明
  3. inline-block 和 float 的区别
  4. Adaptive Images : 为不同的屏幕尺寸提供不同的图片
  5. 一文读懂python本地开发环境配置
  6. libstdc和glibc的一些共享库问题
  7. 决策树:ID3和C4.5
  8. 【181023】有意思的屏幕画笔,基于VC++实现
  9. muduo网络库:05---线程同步精要之(线程安全的Singleton实现)
  10. Java(1):Java SE疯狂复习基本数据类型、OOP
  11. MAC系统下解决Teamviewer 被误认为商业用途的问题
  12. 鸿合一体机触屏没反应怎么办_电脑一体机触摸屏没反应 触摸屏电脑一体机常见故障解决方法...
  13. 一个企业如何运营微商管理系统?
  14. Spring IOC详解 以及 Bean生命周期详细过程 可以硬刚面试官的文章
  15. ERROR 1819 (HY000) Your password does not satisfy the current policy requirements
  16. 微信小程序上传单张或多张图片
  17. 维克森林大学计算机科学专业好不好,维克森林大学商业分析硕士怎么样?
  18. 用strcmp来打印输入三次密码哔哩练习
  19. 成功解决Component template should contain exactly one root element
  20. Revit:Revit无法正常运行外部程序“tangentUIApp”解决办法

热门文章

  1. php单元格,PHP中的单元格怎么利用PhpSpreadsheet进行设置
  2. uniapp上传图片和视频到OSS
  3. 【WPS表格】条件格式功能的部分运用
  4. Linux怎么强制关闭docker 关闭某个进程 查看某个进程 关闭程序 关闭进程
  5. Teebik:2017H1墨西哥畅销榜:超级马里奥入围十强 ARPG市场潜力巨大
  6. 钻石的基本知识(买钻戒的时候需要了解)
  7. ICLOUD储存空间要升级吗_白茶的储存,既要讲究密封,又要保持通风,是开玩笑吗?...
  8. 001 打印机加墨后老是弹出窗口
  9. yii2 php init,Yii2 使用 .env 来配置项目环境变量
  10. 51单总线控制SV-5W语音播报模块