当前位置: 首页 > 科技观察

一文看懂Java类加载器

时间:2023-03-13 22:38:43 科技观察

Java类加载器是个老生常谈的问题,大多数Java工程师也对其中的知识点了如指掌。最近在看源码的时候,发现有些细节还是不清楚,就写一篇文章整理一下。关于Java类加载器的知识,网上搜了很多,自己也看了很多文档和博客。虽然资料很多,但还是希望能通过这篇文章,写一些自己的理解,写一些自己的东西。如果只是重复别人写过的东西,就失去了写作的意义。类加载器结构类加载器结构名称解释:根类加载器,也叫引导类加载器,启动类加载器。由于不属于Java类库,这里不再赘述其对应的类名。很多人喜欢称它为BootstrapClassLoader。本文称它为根类加载器。加载路径:\lib扩展类加载器,对应的Java类名为ExtClassLoader,是sun.misc.Launcher的一个内部类。加载路径:\lib\extApplication类加载器,对应的Java类名为AppClassLoader,是sun.misc.Launcher的一个内部类。加载路径:用户目录//可以这样打印加载路径System.out.println("boot:"+System.getProperty("sun.boot.class.path"));System.out.println("ext:"+System.getProperty("java.ext.dirs"));System.out.println("app:"+System.getProperty("java.class.path"));重点说明:根类加载器对于普通Java工程师来说,可以理解为一个概念性的东西,因为我们无法通过Java代码获取到根类加载器,它属于JVM层面。除了根类加载器之外,另外两个扩展类加载器和应用类加载器都是通过类sun.misc.Launcher初始化的,Launcher类由根类加载器加载。查看Launcher初始化源码:publicLauncher(){Launcher.ExtClassLoadervar1;try{//初始化扩展类加载器,注意构造函数没有参数,即获取不到根类加载器var1=Launcher.ExtClassLoader.getExtClassLoader();}catch(IOExceptionvar10){thrownewInternalError("无法创建扩展类加载器",var10);}try{//初始化应用类加载器,注意这里的入参是扩展类加载器this.loader=Launcher.AppClassLoader.getAppClassLoader(var1);}catch(IOExceptionvar9){thrownewInternalError("无法创建应用程序类加载器",var9);}//设置上下文类加载器,后面会详细讲解Thread.currentThread().setContextClassLoader(this.loader);//删除了一些安全代码//...}双亲委托模型双亲委托模型是指当我们调用类加载器的loadClass方法加载类时,类加载器会先请求其父类加载器加载,而然后递归。如果所有的父类加载器都加载失败,则由当前类加载器自己执行加载操作。逻辑很简单,我们通过ClassLoader类的源码来分析一下。protectedClassloadClass(Stringname,booleanresolve)throwsClassNotFoundException{//执行类加载操作时,先加锁,避免并发加载synchronized(getClassLoadingLock(name)){//先判断指定类是否已经被加载加载类c=findLoadedClass(name);如果(c==null){longt0=System.nanoTime();try{if(parent!=null){//如果当前类没有加载,加载父类如果类加载器不为null,则请求父类加载器进行加载操作c=parent.loadClass(name,false);}else{//如果当前类没有被加载,且父类加载器为null,请求根类加载器执行加载操作c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){}if(c==null){longt1=System.nanoTime();//如果父类加载器加载失败,则当前类加载器加载,c=findClass(name);//做一些统计操作sun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}//初始化类if(resolve){resolveClass(c);}返回c;双亲委派模型的实现逻辑一般都非常简单明了。这里有几个细节需要说明:ClassLoader是一个抽象类,但它不包含任何抽象方法。如果想在不破坏双亲委派模型的情况下实现自己的类加载器,只需要继承ClassLoader类,重写findClass方法即可。如果想实现自己的类加载器,打破双亲委托模型,需要继承ClassLoader类,重写loadClass和findClass方法。混淆系统类加载器当你了解了以上知识后,你会发现在ClassLoader类中有一个方法是getSystemClassLoader,系统类加载器,这是什么?系统类加载器是一个容易混淆的概念。我曾经以为它是应用程序类加载器的别名,就像启动类加载器和根类加载器一样。实际上,默认情况下,我们通过ClassLoader.getSystemClassLoader()获取的系统类加载器确实是应用类加载器。在谈到类加载器结构时,很多资料会直接把应用程序类加载器称为系统类加载器。其实我们可以通过类名来判断两者不是同一个类。可以通过System.setProperty("java.system.class.loader",xxxclassname)自定义系统类加载器。系统类加载器并不是一个全新的加载器,它只是一个概念。本质上就是上面说的四种类加载器(算上自定义类加载器)。需要继续研究。被忽略的上下文类加载器上面讨论了每个类加载器的加载路径。鉴于双亲委派模型的设计,子类加载器保留了父类加载器的引用,也就是说,当子类加载器加载的类需要访问父类加载器加载的类时,毫无疑问,它可以访问。但是考虑一个场景,会不会出现父类加载器加载的类需要访问子类加载器加载的类的情况?如果是,如何解决(父类加载器没有子类加载器的引用)?这就是我们要讨论的经常被忽视的上下文类加载器。经典案例:JDBC是Java开发的一套访问数据库的标准接口。它包含在Java基础类库中,也就是说,它是由根类加载器加载的。同时每个数据库厂商都会实现这套接口让Java工程师访问自己的数据库,而这部分实现类库需要Java工程师在项目中作为第三方依赖引入使用,也就是说,这部分实现类库是由应用程序类加载器加载的。首先,一段获取Mysql连接的Java代码://加载驱动Class.forName("com.mysql.jdbc.Driver");//连接数据库Connectionconn=DriverManager.getConnection(url,user,password));这里的DriverManager类属于Java基类库,由根类加载器加载。我们可以通过它获取到数据库的连接。很明显,它通过com.mysql.jdbc.Driver驱动成功连接到了数据库。也有说数据库驱动(作为第三方类库引入)是由应用类加载器加载的。这种场景就是典型的父类加载器加载的类需要访问子类加载器加载的类。Java是如何实现这种反向访问的呢?直接看DriverManager类的源码://各种参数建立数据库连接的方法最终会到这里privatestaticConnectiongetConnection(Stringurl,java.util.Propertiesinfo,Classcaller)throwsSQLException{//获取调用驱动的类加载器ClassLoadercallerCL=caller!=null?调用者.getClassLoader():null;synchronized(DriverManager.class){//如果为null,则使用上下文类加载器//这里是重点,当类被加载时设备会为null?当然是根类加载器加载的类if(callerCL==null){callerCL=Thread.currentThread().getContextClassLoader();}}//...省略for(DriverInfoaDriver:registeredDrivers){//使用上下文类加载器加载驱动程序if(isDriverAllowed(aDriver.driver,callerCL)){try{//如果加载成功,连接连接con=aDriver.driver.connect(url,info);//...}catch(SQLExceptionex){if(reason==null){原因=前;}}}//...}}重点解释:为什么上下文类加载器可以加载到数据库驱动中?回到一开始Launcher初始化类加载器的源码,我们发现所谓的context类加载器本质上就是应用类加载器。你觉得开悟了吗?上下文类加载器只是为了解决类的反向访问而提出的一个概念。它不是一个全新的类加载器。它本质上是一个应用程序类加载器。基本上,关于Java类加载器的知识,我就了解了这么多。如有未尽或不妥之处,欢迎交流。