类加载器,是用来加载Java类到Java虚拟机中。一般来说,Java虚拟机使用Java类的方式如下: .java->编译->.class->类加载器读取字节码->转换成类的实例->通过此实例的newInstance()方法创建该类的对象。
当然,也可能是你的.class文件来自于动态生成的,或者网络下载的。
基本上所有的Class Loader都是java.lang.ClassLoader类的一个实例。而ClassLoader就是负责根据一个指定的类名称,找到或者生活对应字节码,然后从这些字节码中生成一个java类,即java.lang.Class类的一个实例。当然该类还负责加载Java应用所需的资源如图像文件,配置文件等。
类加载器的组织结构:
类加载器大致分为两种,一种是系统提供的,一种是开发人员自己编写的。
而系统提供的主要有:
- 引导类加载器(bootstrap class loader):用来加载Java核心库,他采用原生代码实现,并不继承自CLassLoader。
- 扩展类加载器(extensions class loader): 用来加载Java扩展库,也就是lib/ext包。
- 系统类加载器(system class loader): 根据应用程序的classpath来加载类,java应用程序类都是由他完成加载的,可以通过ClassLoader.getSystemClassLoader()得到她。
而开发人员自己的类加载器就需要通过继承ClassLoader来重写,以满足一些特殊的需求。
类加载器的组织结构类似一个树结构,分层的,根节点是 引导类加载器
类加载器的代理模式
类加载器在尝试自己去超找某个类的字节码并定义它的时候,会先代理给其父加载器,由父加载器先去尝试加载这个类,一次类推。
那么他为什么要这么做的,使用代理模式?
首先我们要先理解JVM是如何判断2个类相同的。JVM要检查类全名是否相同并且是否同一个类加载器加载。只有都相同,才认为相同。即便是同样的字节码,被不同的类加载器加载,也是不同的。
所以使用代理模式,可以保证Java核心类库的安全。举个例子:所有类都是集成自Object,而Object是引导类加载器负责完成,而如果开发者自己定义了这个类,并加载了,从而存在多个版本的Object。那么势必会造成这些类的子类之间的兼容问题。
另外还需要注意的是,不同的类加载器为相同名称的类创建了一个额外的名称空间。相同名称的类可以并在在JVM中,只需用不同的类加载器完成加载即可。不同类加载器加载的类是不兼容的,也就是相当于JVM内部创建了一个相互隔离的Java类空间
加载类的过程
前面说到,类加载器会首先代理给其他类加载器来尝试完成
加载某个类。这就意味着真正完成加载工作和
启动这个加载过程的类加载器可能不是同一个。简言之就是:加载由其他类加载器完成,启动由自己的类加载器启动。
真正完成类加载工作是通过调用defineClass来实现的;而启动类的加载过程是通过调用loadClass()来实现。
defineClass称为类的定义加载器,会抛出NoClassDefFoundError
loadClass称为类的初始加载器,会抛出ClassNotFoundException
JVM判断两个类是否相同的时候,就是使用类的定义加载器。也就是说哪个class loader启动这个类并不重要,重要的是由谁负责定义这个类。
那么,两个加载器的联系是什么呢? 一个类的定义加载器是引用其他类的初始加载器。