如何理解和运用ClassLoader

 更新时间:2021年01月30日 14:47:20   作者:wsMrLin  
这篇文章主要介绍了如何理解和运用 ClassLoader,帮助大家更好的理解和使用JVM,感兴趣的朋友可以了解下

定义

根据《深入理解Java虚拟机》提到“通过一个类的全限定名(packageName.ClassName)来获取描述此类的二进制字节(class文件字节)这个动作的代码模块就叫做类加载器(ClassLoader)”。

作用

1、通常类加载器的作用是加载资源(字节码文件)到java虚拟机中,想要在一个jvm 进程中唯一确认一个类,除了类的全限定名外,还需要指定它是由哪个类加载器加载的。
2、比如我们的类库需要通过远程网络获取,可以通过自定义类加载器从远程加载字节码文件。
3、java的字节码文件很容易反编译出来,一些核心的代码不想被反编译出来,可以对字节码进行加密,然后通过自定义的类加载器加载这些字节码,然后进行解码返回给虚拟机。
4、比如jvm的热加载和热部署等功能也需要自定义类加载器来完成。
.......

java类加载器的种类

1、Bootstrap ClassLoader : 该加载器是最顶层的类加载器,它是加载放在{Java_home}\lib目录 或者-Xbootclasspath指定路径下类库。

2、Extension ClassLoader : 该类加载器负载加载{Java_home}/lib\ext目录 或者System.getenv("java.ext.dirs")系统变量路径下的类库。

3、Application ClassLoader : 该类加载器加载用户程序类路径下的类库,它是默认的程序的类加载器。

双亲委派机制

1、双亲委派机制,双亲委派除了启动类加载器(Bootstrap ClassLoader)外,其他的类加载器都应该有自己父加载器。它们的实现不是通过继承来实现的,而是通过组合的方式。当加载某个Class时,当前类加载器会把这个加载请求委派给其父类加载器加载,同理父类加载器同样委派其它的父类加载器加载,直到无其父类类加载器加载为止,如果父类加载器加载失败,才会由其子类加载。

2、使用双亲委派机制,有个明显的特征是:Java类随着它的类加载器一起具备了一种优先级的层次关系。比如rt.jar包中的java.lang.Object,由于它所在位置是由启动类加载器加载,所以Object类在程序的各种类加载器环境中都是同一个类。

3、双亲委派模型如下:

ClassLoader

可以看下ClassLoader 双亲委派模型的大致代码框架如下:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
  {
    synchronized (getClassLoadingLock(name)) {
      // 1、查看该类是否加载
      Class<?> c = findLoadedClass(name);
      if (c == null) {
        long t0 = System.nanoTime();
        try {
 		  // 2、如果未加载,委托给父类加载器加载
          if (parent != null) {
            c = parent.loadClass(name, false);
          } else {
          //3、没有父类加载器,委托给BootstrapClassLoader
            c = findBootstrapClassOrNull(name);
          }
        } catch (ClassNotFoundException e) {
          // ClassNotFoundException thrown if class not found
          // from the non-null parent class loader
        }

        if (c == null) {
          // 父类加载器没有加载到,则自己加载
          long t1 = System.nanoTime();
          c = findClass(name);

          // 记录该类加载的状态Stat. 
        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
          sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
          sun.misc.PerfCounter.getFindClasses().increment();
        }
      }
 	  // resolve :true,需要对类进行链接(链接阶段包括:准备,解析,初始化类)
      if (resolve) {
        resolveClass(c);
      }
      return c;
    }
  }

1、通过以上可以知道,我们可以继承ClassLoader 来实现自己的类加载器,然后重写findClass()方法,这些还是保存了双亲委派机制。
2、当我们重写findClass()方法时,得到该类的字节码后,需要调用defineClass()来放回Class<?>对象。

URLClassLoader

1、一般自定义的类加载器可以直接继承该类,该类加载器通过添加url路径来获取类。
2、可以在其构造函数上URLClassLoader(URL[] urls, ClassLoader parent)直接进行添加其URL。
3、也可以通过addURL(URL)添加其URL。

自定义类加载器

1、首先假设main-project为我们自己编写的工程,其依赖某一api:service-spi。而service-spi的实现有很多,project-spi-impl是其中的一个。
2、当main-project仅依赖service-spi,而project-spi-impl不在工程的类加载路径下。所以需要自定义类加载器,从某个路径下的jar加载进来。CoreClassLoader就是自定义的类加载器。
3、CoreClassLoader继承自URLClassLoader,然后把相关搜索路径添加到该类加载器即可。
4.github:https://github.com/zhvqee/class-loader

SpringBoot 对类加载器的运用

1、SpringBoot 工程通过spring-boot-maven-plugin插件打包。把相关资源和依赖包都打到一个jar包中(all in one)。其包的结构如下:

BOOT-INF/classes:存放的是本工程的class文件
BOOT-INF/lib:存放的是本工程依赖的二方包和三方包。
META-INF/MANIFEST.MF:该文件记录了程序启动入口等。
o.s.b.loader包下就是springboot自定义的类加载器。

2、当我们执行java -jar xxxx 时,会读取MANIFEST.MF下
Main-Class: org.springframework.boot.loader.JarLauncher,
该配置就是程序的路口。而我们编写的Main方法不是真正的启动的入口。

3、当执行Launcher#launch()方法时,会把SpringBoot自定义的类加载器LaunchedURLClassLoader设置线程的上下文中,并通过该自定义类加载器加载我们自己编写的main方法所在的类,然后利用反射调用main方法。代码片段如下:

Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });

4、LaunchedURLClassLoader 类加载器继承URLClassLoader类加载器,它加载的路径就是可执行jar下BOOT-INF下的class文件和二方包、三方包。

Class.forName() 和 ClassLoader.load()

1、JVM 加载的类主要经过以下几个步骤:加载,链接,初始化,试用,卸载。
2、Class.forName()默认是需要对加载的类进行初始化。
3、ClassLoader.load实际调用的是ClassLoader.load(className,false),false:表示不进行链接,不进行链接也就代表不会进行初始化的操作,类的静态块和静态对象都不会执行。

以上就是如何理解和运用ClassLoader的详细内容,更多关于理解和运用 ClassLoader的资料请关注脚本之家其它相关文章!

相关文章

  • 老生常谈spring boot 1.5.4 日志管理(必看篇)

    老生常谈spring boot 1.5.4 日志管理(必看篇)

    下面小编就为大家带来一篇老生常谈spring boot 1.5.4 日志管理(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Java 高并发一:前言

    Java 高并发一:前言

    本系列基于炼数成金课程,为了更好的学习,做了系列的记录。 本文主要介绍 1.高并发的概念,为以后系列知识做铺垫。 2.两个重要的定理
    2016-09-09
  • Spring中property-placeholder的使用与解析详解

    Spring中property-placeholder的使用与解析详解

    本篇文章主要介绍了Spring中property-placeholder的使用与解析详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • SpringBoot中@EnableAsync和@Async注解的使用小结

    SpringBoot中@EnableAsync和@Async注解的使用小结

    在SpringBoot中,可以通过@EnableAsync注解来启动异步方法调用的支持,通过@Async注解来标识异步方法,让方法能够在异步线程中执行,本文就来介绍一下,感兴趣的可以了解一下
    2023-11-11
  • Java经典面试题汇总:Spring Boot

    Java经典面试题汇总:Spring Boot

    本篇总结的是Spring-Boot框架相关的面试题,后续会持续更新,希望我的分享可以帮助到正在备战面试的实习生或者已经工作的同行,如果发现错误还望大家多多包涵,不吝赐教,谢谢
    2021-07-07
  • springBoot使用openfeign来远程调用的实现

    springBoot使用openfeign来远程调用的实现

    这篇文章主要介绍了springBoot使用openfeign来远程调用的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java ArrayList扩容问题实例详解

    Java ArrayList扩容问题实例详解

    这篇文章主要介绍了Java ArrayList扩容问题实例详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • Java实现根据地址智能识别省市区县

    Java实现根据地址智能识别省市区县

    这篇文章主要为大家详细介绍了如何编写一个Java工具类,可以根据身份证地址或用户输入的地址,智能识别并提取出详细的省市区县信息,感兴趣的小伙伴可以了解下
    2025-03-03
  • Java中HashMap的put过程详解

    Java中HashMap的put过程详解

    这篇文章主要介绍了Java中HashMap的put过程详解,HashMap有4个构造器,其他构造器如果用户没有传入initialCapacity 和loadFactor这两个参数,会使用默认值一般如果new HashMap()不传值,需要的朋友可以参考下
    2023-07-07
  • 序列化版本号serialVersionUID的作用_动力节点Java学院整理

    序列化版本号serialVersionUID的作用_动力节点Java学院整理

    Java序列化是将一个对象编码成一个字节流,反序列化将字节流编码转换成一个对象,这篇文章主要介绍了序列化版本号serialVersionUID的作用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05

最新评论