c 自定义实现string类 clear_有关类加载器的总结

b264b48d4700d0c4e4cce9cdb1ba1b9c.png

对于java开发来说,classLoader往往是容易被我们忽视的一个重要知识点。而classLoader对java的发展也有很大的影响。例如。

  1. 早期的applet应用,通过网络远程加载class文件。
  2. web容器对应用class文件的加载。
  3. spi机制的支持。
  4. 等等。

学习知识往往带着疑问去学习,效果可能会比较好。接下来我带着几个问题来解答classLoader有关的疑问。

  1. 什么是classLoader?为什么需要classLoader?
  2. 什么是双亲委派? 为什么需要双亲委派?
  3. 什么是contextClassLoader? 为什么需要ContextClassLoader?
  4. classLoader用于什么场景?什么情况下需要什么classLoader?

接下来就一一解答上面的疑问。以我的学习方式来说,对这块知识点用反证的思维来理解比较简单。

分析classLoader之前,我们先直接class的加载过程,并且下面方法跟加载过程一一对应。

e7674ac929a11dbd6cf33780d5857294.png
类加载过程 , copy from http://www.importnew.com/25295.html
  • 加载class文件,从jvm中对应的Class文件。
  • 接着验证Class是否合法。即对class进行有效性、合法性验证。
  • 准备阶段的工作:为类变量分配内存并初始化值,这里的初初始化值并不是设置用户指定的值。例如。
public static int age = 12;
public static String name = "wang007";
初始化值,先将int = 0; name = null;在接下来的初始化阶段, 再讲 int = 12, name = "wang007";

其他阶段可参考JVM 类加载机制详解 - ImportNew;

1. 什么是classLoader?为什么需要classLoader?

classLoader是用于加载我们编译好的class文件到jvm中。在ClassLoader这个类中能看到相关重要的方法。下面我就列举几个比较重要的方法来解释一下。

public 

loadClass方法,根据 java全限定名称 加载 很明显,该方法对应类加载过程的 加载阶段

findClass方法, 该方法是需要子类去实现的方法, 根据 java全限定名称 加载 class文件。 可以从本地文件系统加载, 也可以通过网络从任一服务器中加载。 对于需要自实现classLoader的话,一般只需要实现该方法即可。

resolveClass方法,根据loadClass方法加载好的Class对象, 进行连接工作。 即验证、准备、解析方法。

还有defineClass方法,将用byte[]二进制数组 创建class对象。final方法无法拓展,在findClass方法中,获取byte[],调用defineClass方法创建Class对象。


2 什么是双亲委派? 为什么需要双亲委派?

说到双亲委派模型,那么必须先介绍BootstrapClassLoader,ExtClassLoader,AppClassLoader.

  • BootstrapClassLoader负责加载rt.jar包中的class文件。 C++写的,所以在java不可见,用null引用代替。
  • ExtClassLoader负责加载ext目录的jar包中的class文件。
  • AppClassLoader负责加载classpath下的class文件,即我们编写的java文件编译成的class文件。

所谓的双亲委派模型是指加载class时, 先让父classLoader加载class,如果父classLoader加载不到,再自己加载。 这里的“父”, 不是指继承关系,而是指属性指定的“父”ClassLoader。

AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是BootstrapClassLoader。

public 
在ClassLoader这个类中, 用parent属性指定当前classLoader的“父”加载器。 AppClassLoader,ExtClassLoader都是继承该类。

为什么需要双亲委派?

双亲委派模型是为了保证应用安全的,确保rt.jar包内的代码只由BootstrapClassLoader加载的。 例如我们在代码中用AppClassLoader(即SystemClassLoader)加载String.class,AppClassLoader会让ExtClassLoader加载的, ExtClassLoader会让BootClassLoader加载,因为String.class在rt.jar中,所以BootstrapClassLoader会加载成功, 返回到 ExtClassLoader, 最后返回到AppClassLoader中。

如果我们用自实现的ClassLoader加载String.class(这个String.class我加点后门代码),不通过双亲委派模型,直接自己加载。是可以加载出来的,但是跟BootstrapClassLoader加载出来的String不是同一个。因为ClassLoader不同, 加载出来的Class对象也不同。 如果让自定义ClassLoader加载出来的String 赋值给BootClassLoader,会报ClassCastException.

这里展示双亲委派的关键代码

//这里指定 resolve = false,所以ClassLoader加载class是不会导致类的初始化的

有疑惑点的地方,再详情说说。

  1. 整个加载过程,都是锁住的,确保指定对当前类的全限定名加载一个线程在执行。
  2. 如果自定义classLoader设置parent = null, 那么就会跳ext路径下类加载,可能会导致冲突。 所以自定义ClassLoader一般指定 parent = appClassLoader。

3. 什么是contextClassLoader? 为什么需要ContextClassLoader?

ContextClassLoader就是通过thread.getContextClassLoader()方法获取的ClassLoader。 我知道,如果我换做你,听到这种解释, 我都想锤死我自己。哈哈哈。

上面说的双亲委派模型, 看似天衣无缝,其实未必。我觉得ContextClassLoader恰恰是为双亲委派模型擦屁股的角色。

当我们需要加载一个Class的对象,从自定义ClassLoader,到appClassLoader,再到ExtClassLoader,最后到BootstrapClassLoader。没问题, 很顺利。这是从下到上加载。 但是反过来,当从上到下加载的时候,这个变得是一个不可能完成的任务。为了弥补这个缺陷, 特定设计的ContextClassLoader。

但可能又会有疑问了, 什么情况下, 会出现从上到下加载呢?

我举个很实用,但是又几乎用不到的例子吧(是不是像极了好吃又吃不饱的东西。233333) spi机制。 这里我就不展开讲spi了。有兴趣的话,自行搜索资料学习,基本大部分框架都会用到spi。

我们都知道,SUN是个一流的公司,因为一流的公司卖标准嘛, 虽然这个一流的公司已经死了。 在rt.jar包中, 定义了很多spi接口, JDBC,JNDI等等。 但是spi接口的实现一般都是以第三方jar的方式提供到classpath下。 例如, JDBC spi的接口由BootstrapClassLoader加载, 但是有些spi接口中会加载spi实现包的类,而BootstrapClassLoader只能加载rt.jar的类, 不能classPath下的类,所以通过thread.getContextClassLoader方法获取到ClassLoader(这个一般是AppClassLoader)。然后通过这个classLoader在spi 接口中加载其实现。

补充一个知识点。如果类A引用类B,且类B还未加载, 那么用加载类A的类加载器加载类B。

我找了下,只找到了DriverManger这个类下,通过spi机制加载Driver实现。(例如mysql driver)只不过这个用的是ClassLoader.getSystemClassLoader()方法, 效果跟contextClassLoader差不多。都是为了加载第三方实现类。

public 

接下来来看下。ContextClassLoader是怎么来的。

public 

在Launcher的构造方法中,获取ExtClassLoader,如果是第一次调用,就会创建一个ExtClassLoader。 获取再获取AppClassLoader。第一次调用, 也会创建一个。 最后将appClassLoader设置到Thread。

我们知道,main方法是java程序的入口,而main方法所在的类也是有AppClassLoader加载的。 而AppClassLoader正在来自于Launcher类,所以在Launcher类在main方法运行之前就创建好了。

为啥AppClassLoader也叫SystemClassLoader呢? 因为:

public 

在ClassLoader类中的getSystemClassLoader方法,就是从launcher类中获取的appClassLoader,并设置到ClassLoader类的静态属性中。 所以AppClassLoader也叫SystemClassLoader

ContextClassLoader的传播。

//thread的构造方法中调用init方法    

在新建一个线程的时候,就是会把原来线程所在的ClassLoader设置到新的线程中。 就这么一直传播下去。 如果我们改变当前线程的ContextClassLoader,那么当前线程上新建Thread的ContextClassLoader就是改变后的。

例如。

public 

所以ContextClassLoader的传播是通过新建Thread的时候,把当前线程的ContextClassLoader设置到新线程中。

同时还有类似方式传播的东西。inheritableThreadLocals。 也叫可继承的ThreadLocal。有兴趣的话,可以自行查找资料。InheritableThreadLocal。跟ThreadLocal差不多的作用。

4. ClassLoader的运用场景。

其实ClassLoader,我们直接或间接的用了很多。例如Tomcat需要加载项目中的class文件。 按照默认提供的ClassLoader是不够的。必须自定义ClassLoader来加载项目中的class文件。还有热启动机制,想必也是用到了自定义ClassLoader。

最后再解决一个疑问点。 Class.forName与ClassLoader.loadClass 的区别?

前面介绍了ClassLoader的loadClass方法,是不会触发类的初始化操作的。

而Class.forName是会触发类的初始化操作。

public 

在forName0方法中,第一个参数就是类名,第二个参数是是否初始化类, 第三个参数是指定ClassLoader。 所以在forName方法中, 指定了需要初始化, 还指定了ClassLoader为当前调用类的ClassLoader。

其实Class.forName还有一个重载方法,可以指定是否初始化,和ClassLoader

public 

好了,有关classLoader的知识就总结到这里了。 有些点由于篇幅关系就没有深入下去了。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部