简介
虚拟机的设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节码流”,把这动作放到虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称之为类加载器
类加载器负责加载所有的类,它为所有被载入内存中的类生成了一个java.lang.Class实例对象
- 任何一个类,在内存(堆)中只会有一个Class实例对象
- new出来的对象都可以理解为这个Class对象的映射
- 唯一标识符
类加载器分类
- 启动类加载器
- 主要加载的是JVM自身需要的类,这个类加载使用C++实现,是虚拟机自身的一部分
- 所有的包名为java.开头的类均被 BootstrapClassLoader加载
- 扩展类加载器
- 负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类)
- 系统类加载器
- 负责加载用户类路径(ClassPath)所指定的类
- 线程上下文类加载器
- 很特殊的一种
- 类似Thread.currentThread().getContextClassLoader()获取线程上下文类加载器
- 线程上下文加载器其实很重要,它违背(破坏)双亲委派模型,很好地打破了双亲委派模型的局限性,尽管在开发中很少用到,但是框架组件开发要频繁使用到线程上下文类加载器,如Tomcat等等…
除了上述的类加载器之外,在必要时,我们还可以自定义类加载器,通过继承ClassLoader类,重写其中的loadClass方法,生成Class对象。
自定义类加载器示例:
1 | public static void main(String[] args) throws Exception { |
自定义类加载器流程:
- 根据全限定类名获取到文件名
- 根据文件名获取到输入流(InputStream)
- 将输入流的数据存入byte数组
- 使用defineClass函数,将根据全限定类名和byte数组,生成Class对象
注意:一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader(系统/应用类加载器)。
命名空间
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。
- 同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
在不同的命名空间中,有可能会出现类的完整名字相同的两个类。
由子加载器加载的类能看见父加载器的类,由父亲加载器加载的类不能看见子加载器加载的类
- 只有在相同的命名空间中,每个类才只能被加载一次
- 反过来说就是一个类在不同的命名空间中是可以被加载多次的,而被加载多次的Class对象是互相独立的!
- 这就是因为不同的类加载器加载同一个class文件,会生成不同的Class对象
结论:
- 加载一个类(File1)的时候,这个类里面调用了其他的类(File2)或者其他类方法的初始化代码,那么这里面的类也会试着从这个类的加载器开始向上委托加载,如果全都加载不了就报NoClassDefFoundError异常。
- 父亲加载器加载的类(File2)不能看见子加载器加载的类(File1)
- 由子加载器加载的类(File1)能看见父加载器的类(File2)
- 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载类相互不可见。
类加载机制与双亲委派模型
全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
双亲委派:
- 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
- 当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
- 如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
- 若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常 ClassNotFoundException。
各个类加载器继承关系图:
顶层的类加载器是抽象类ClassLoader类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
ClassLoader源码中的loadClass(String)方法该方法加载指定名称(包括包名)的二进制类型,loadClass()方法是ClassLoader类自己实现的,该方法中的逻辑就是双亲委派模式的实现
loadClass():
1 | public Class<?> loadClass(String name) throws ClassNotFoundException { |
双亲委派模型的意义:
- 系统的防止内存中出现多份同样的字节码
- 因为存在缓存机制
- 保证Java程序的安全稳定运行
- 假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
参考
- 本文作者: xczll
- 本文链接: https://xczllgit.github.io/2020/03/30/jvm/2020-03-30-classLoader/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!