JVM基础之类加载器

来源:互联网 发布:unity3d窗口最大化 编辑:程序博客网 时间:2024/06/06 12:53

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在JVM中的唯一性。
所以,即便该类class文件相同,如果加载它的类加载器不同,那这两个类就必定不相等。
(这里的相等包括equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括instanceof关键字做所属关系判断等)

类加载器分类

Bootstrap ClassLoader -启动类加载器,它负责加载Java的核心类。负责将存放在\lib目录中的,或者被-Xbootclasspath参数所所指定的路径中的,并且是JVM识别的类库加载到JVM中。
(仅按照文件名识别,如rt.jar,名字不符合的类库就算放在lib下也不会被加载)
这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的,无法被程序直接引用。

Extension ClassLoader -扩展类加载器,它由sum.misc.Launcher$ExtClassLoader实现。
负责加载JRE的扩展目录中jar包中的类(/lib/ext或者被java.ext.dirs系统变量所指定的路径)
在extension classloader调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。

Application ClassLoader -系统类加载器(应用程序类加载器),它由sum.misc.Launcher$AppClassLoader实现,可以通过ClassLoader.getSystemClassLoader()返回。它负责在JVM被启动时,加载用户类路径(ClassPath)上所指定的类库。(即来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH*作系统属性所指定的JAR类包和类路径)
如果没有特别指定,则用户自定义的类加载器是system classloader的子类。

双亲委派模型

应用程序由这三种类加载器互相配合进行加载,类加载器之间的层级关系就是“类加载器的双亲委派模型”。
除了启动类加载器之外,其余类加载器都有自己的父类。
Bootstrap ClassLoader是Extension Classloader的父类,Extension Classloader是Application ClassLoader的父类。
注意:类加载器之间的父子关系不是以继承的方式实现的,而是使用组合的方式来复用父加载器的代码。
这里写图片描述
一个类加载器收到类加载的请求时,会先用父类加载器加载,每层的类加载器都是这样,所以所有的加载请求最终都会被传到Bootstrap ClassLoader中。只有当父类加载器找不到这个类时,子类加载器才会自己去加载。

双亲委派模型保证了一个类在各种类加载器环境中都是同一个类(安全性,避免自定义的类覆盖了核心类对JVM造成破坏),例如无论什么类加载器来加载Object,最终都会被委派给Bootstrap ClassLoader进行加载,因此保证了自定义的Object虽然在程序中被正常编译,但永远无法被加载运行。因为类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它(所以Object被加载过,那就直接返回,而不再加载自定义的Object),如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因.

类加载器之间用的组合模式进行父类加载器代码复用,其实也就相当于将所有代码写在一个类上,但是这样的话也是按照类路径进行优先级加载,为了区分不同路径下相同全限定名的类,那也需要一个类型来标注他们是不同的类,所以拆出路径对类加载器进行分层,是一种优雅设计。

最后,双亲委派模型不是强制性的约束模型。比如java中加载SPI接口中的实现代码,但启动类加载器明显找不到这个SPI的实现类,所以只能请求子类加载器去加载这个实现类(这里违反了类加载器层级之间的请求顺序)。再比如热部署中,除了核心类需要父类加载器进行加载(因为不会变,放入了cache中),其余程序的类都由这个类所在的模块的类加载器进行加载。(这里没有再将所有的类交给父类加载器加载,而是模块类加载器加载)

原创粉丝点击