如何理解和运用ClassLoader

定义

根据《深入理解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的资料请关注www.887551.com其它相关文章!

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐