乘风原创程序

  • 如何理解和运用ClassLoader
  • 2021/1/30 11:06:42
  • 定义

    根据《深入理解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:

    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的资料请关注本教程网其它相关文章!