类加载器(双亲委派模型)

迷南。 2022-05-23 00:19 328阅读 0赞

1.类与类加载器

  1. 对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。(比较两个类是否'相等',只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源一个同一个class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等)
  2. 这里所指的‘相等’,包括类的Class对象的equals()方法,isAssignableFrom()方法,isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判断等情况。
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. public class ClassLoaderTest {
  6. public static void main(String[] args) throws Exception {
  7. ClassLoader myLoader = new ClassLoader() {
  8. @Override
  9. public Class<?> loadClass(String name) throws ClassNotFoundException {
  10. try {
  11. String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
  12. InputStream is = getClass().getResourceAsStream(fileName);
  13. if(is==null){
  14. return super.loadClass(name);
  15. }
  16. byte[] b=new byte[is.available()];
  17. is.read(b);
  18. return defineClass(name,b,0,b.length);
  19. } catch (IOException e) {
  20. throw new ClassNotFoundException(name);
  21. }
  22. }
  23. };
  24. Object obj =myLoader.loadClass("ClassLoaderTest").newInstance();
  25. System.out.println(obj.getClass());
  26. System.out.println(obj instanceof ClassLoaderTest);
  27. }
  28. }
  29. ![70][]
  30. 这个类加载器加载名为ClassLoaderTest的类并实例化了这个类的对象。从两行输出结果可以看到,第一行输出确定是类ClassLoaderTest实例化的对象,但是第二句可以发现这个对象与类ClassLoaderTest做的属性检查却返回了false,这是因为虚拟机中存在了两个ClassLoaderTest,一个是系统应用程序类加载器加载的,一个是我们自定义的类加载器加载的,虽然是同一个Class文件但是却是独立的两个类,做的对象所属类型检查时结果就是false.

2.双亲委派模型

  1. java虚拟机角度来讲只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器用c++实现(只限于HotSpot),是虚拟机自身的一部分;另一种是所有其他的类加载器,由java实现,独立于虚拟机外部,并且都继承自抽象类java.lang.ClassLoader
  2. 从开发人员的角度来看,类加载器可以划分的更细致点,绝大部分java程序都会用到以下三种系统提供的类加载器。
  3. 1.启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在<JAVA\_HOME>\\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用,在编写自定义类加载器是,如果需要把加载请求委派给引导类加载器,那直接用null代替即可。

70 1

  1. 2.扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA\_HOME>\\lib\\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  2. 3.应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

70 2

  1. 双亲委派模型要求除了顶层启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器直接的父子关系不会以继承实现,是使用组合关系来复用父加载器的代码。
  2. 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层启动类加载器中,只有当父加载器反馈自己无法完成这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己加载。(双亲委派模型不是强制性的约束模型)
  3. 使用双亲委派模型来组织类加载器直接的关系,显而易见的好处就是java类随它的类加载器一起具备了一种带有优先级的层次关系。如java.lang.Object,它存放在rt.jar.无论哪个类加载器加载这个类,最终都是委派给最顶端的启动类加载器进行加载。因此Object类在程序的各种类加载器环境中都是一个类。否则如果没有使用双亲委派模型,由各个类加载器自行加载,再程序的ClassPath中存放用户自己编写的java.lang.Object类。那系统中将会出现多个不同的Object类,应用程序将会一片混乱。
  4. 实现双亲委派的代码集中在java.lang.ClassLoaderloadClass()方法中:先检查是否已经被加载过,如果没有加载则调用父加载器的loadClass(),若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,抛出ClassNotFoundException异常后,再调用findClass方法进行加载。

70 3

  1. protected Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException
  3. {
  4. synchronized (getClassLoadingLock(name)) {
  5. // First, check if the class has already been loaded
  6. Class<?> c = findLoadedClass(name);
  7. if (c == null) {
  8. long t0 = System.nanoTime();
  9. try {
  10. if (parent != null) {
  11. c = parent.loadClass(name, false);
  12. } else {
  13. c = findBootstrapClassOrNull(name);
  14. }
  15. } catch (ClassNotFoundException e) {
  16. // ClassNotFoundException thrown if class not found
  17. // from the non-null parent class loader
  18. }
  19. if (c == null) {
  20. // If still not found, then invoke findClass in order
  21. // to find the class.
  22. long t1 = System.nanoTime();
  23. c = findClass(name);
  24. // this is the defining class loader; record the stats
  25. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  26. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  27. sun.misc.PerfCounter.getFindClasses().increment();
  28. }
  29. }
  30. if (resolve) {
  31. resolveClass(c);
  32. }
  33. return c;
  34. }
  35. }

jdk1.8的loadclass方法。

3.“破坏”双亲委派模型

  1. 历史上一共有三次双亲委派模型被”破坏”的情况。

第一次是因为向前兼容JDK1.2以前的版本所添加的protected方法findClass(),在此之前用户去继承ClassLoader的唯一目的就是为了重写loadClass()方法。

第二次是为了能让基础类调用用户代码。如典型的例子JNDI,为了解决这个问题引入了线程上下文加载器,通过线程上下文使父类加载器请求子类加载器完成类加载,采用这种模式的例有:JNDI,JDBC,JCE,JAXB,JBI等(所有涉及SPI的加载动作几乎都采用这种模式)。

第三次是由于用户对程序动态性的追求导致的(代码热替换,模块热部署)。其中OSGI中对类加载器的使用十分值得学习。

(ps:因为篇幅的关系这里对破坏双亲委派模型这块不做过多讲述,有需要的同学请自行查询资料。如果发现有什么纰漏或是说错的地方请留言。内容采自深入理解java虚拟机 jvm高级特性与最佳实践

发表评论

表情:
评论列表 (有 0 条评论,328人围观)

还没有评论,来说两句吧...

相关阅读

    相关 双亲委派模型

    1.类与类加载器     对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。(比较两个