Java类加载器算是一个老生常谈的问题,大多Java工程师也都对其中的知识点倒背如流,最近在看源码的时候发现有一些细节的地方理解还是比较模糊,正好写一篇文章梳理一下。
关于Java类加载器的知识,网上一搜一大片,我自己也看过很多文档,博客。资料虽然很多,但还是希望通过本文尽量写出一些自己的理解,自己的东西。如果只是重复别人写的内容那就失去写作的意义了。
类加载器结构
名称解释:
//可以通过这种方式打印加载路径 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"));
重点说明:
看下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 = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } //设置上下文类加载器,这个后面会详细说 Thread.currentThread().setContextClassLoader(this.loader); //删除了一些安全方面的代码 //... }
双亲委派模型是指当我们调用类加载器的loadClass方法进行类加载时,该类加载器会首先请求它的父类加载器进行加载,依次递归。如果所有父类加载器都加载失败,则当前类加载器自己进行加载操作。
逻辑很简单,通过ClassLoader类的源码来分析一下。
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { //进行类加载操作时首先要加锁,避免并发加载 synchronized (getClassLoadingLock(name)) { //首先判断指定类是否已经被加载过 Class> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //如果当前类没有被加载且父类加载器不为null,则请求父类加载器进行加载操作 c = parent.loadClass(name, false); } else { //如果当前类没有被加载且父类加载器为null,则请求根类加载器进行加载操作 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { long t1 = 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); } return c; } }
双亲委派模型的实现逻辑总体看还是非常简单明了的。
这里有几个细节需要说明:
当你把上面的知识都搞清楚以后,会发现ClassLoader类中有个方法是getSystemClassLoader,系统类加载器,这又是什么?
系统类加载器是个容易让人混淆的概念,我一度以为它就是应用类加载器的别名,就跟启动类加载器和根类加载器道理一样。事实上, 默认情况下我们通过ClassLoader.getSystemClassLoader()获取到的系统类加载器也确实是应用类加载器 。
很多资料在说类加载器结构的时候会直接把应用类加载器说成是系统类加载器,其实我们通过类名就可以判断两个不是一回事。
系统类加载器可以通过System.setProperty("java.system.class.loader", xxx类名)进行自定义设置。
系统类加载器不是一个全新的加载器,它只是一个概念,本质上还是上述说的四大类加载器(把用户自定义类加载器算进去),至于提出这个概念的原因以及使用场景,还需要继续考究。
上面讨论了各个类加载器的加载路径。鉴于双亲委派模型的设计,子类加载器都保留了父类加载器的引用,也就是说当由子类加载器加载的类需要访问由父类加载器加载的类时,毫无疑问是可以访问到的。但考虑一种场景,会不会有 父类加载器加载的类需要访问子类加载器加载的类 这种情况?如果有,怎么解决(父类加载器并没有子类加载器的引用)?
这就是我们要讨论的常常被人们忽略的上下文类加载器。
经典案例:
JDBC是Java制定的一套访问数据库的标准接口,它包含在Java基础类库中,也就是说它是由根类加载器加载的。与此同时,各个数据库厂商会各自实现这套接口来让Java工程师可以访问自己的数据库,而这部分实现类库是需要Java工程师在工程中作为一个第三方依赖引入使用的,也就是说这部分实现类库是由应用类加载器进行加载的。
先上一段Java获取Mysql连接的代码:
//加载驱动程序 Class.forName("com.mysql.jdbc.Driver"); //连接数据库 Connection conn = DriverManager.getConnection(url, user, password);
这里DriverManager类就属于Java基础类库,由根类加载器加载。我们可以通过它获取到数据库的连接,显然是它通过com.mysql.jdbc.Driver驱动成功连接到了数据库,上面也说了数据库驱动(作为第三方类库引入)是由应用类加载器加载的。这个场景就是典型的由父类加载器加载的类需要访问由子类加载器加载的类。
Java是怎么实现这种逆向访问的呢?直接看DriverManager类的源码:
//建立数据库连接各个不同参数的方法最终都会走到这里 private static Connection getConnection( String url, java.util.Properties info, Class> caller) throws SQLException { //获取调用者的类加载器 ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { //如果为null,则使用上下文类加载器 //这里是重点,什么时候类加载器才会为null? 当然就是由根类加载器加载的类了 if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } //...省略 for(DriverInfo aDriver : registeredDrivers) { //使用上下文类加载器去加载驱动 if(isDriverAllowed(aDriver.driver, callerCL)) { try { //如果加载成功,则进行连接 Connection con = aDriver.driver.connect(url, info); //... } catch (SQLException ex) { if (reason == null) { reason = ex; } } } //... } }
重点说明:
为什么上下文类加载器就可以加载到数据库驱动呢?回到上面一开始Launcher初始化类加载器的源码,我们发现原来所谓的上下文类加载器本质上就是应用类加载器,有没有豁然开朗的感觉? 上下文类加载器只是为了解决类的逆向访问提出来的一个概念,并不是一个全新的类加载器,它本质上就是应用类加载器 。
基本上我理解的Java类加载器就这么多知识,如果有没提到的或者是错误的地方,欢迎交流。
新闻名称:一篇文章读懂Java类加载器
文章来源:http://www.shufengxianlan.com/qtweb/news43/440343.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联