如何理解和运用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的资料请关注其它相关文章!