ClassLoader翻译过来就是类加载器,ClassLoader的具体作用就是将class文件加载到JVM虚拟机中去,程序就可以正确运行了。但是,JVM启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。
Class文件
class
文件时字节码格式文件,Java虚拟机并不能直接识别我们平常编写的Java源文件,所以需要javac
这个命令转换成.class
文件。另外,如果用C或者python编写的程序正确转换成class文件后,Java虚拟机也是可以识别运行的。
ClassLoader概念
ClassLoader
是用来动态的加载class文件到虚拟机中,并转换成java.lang.class
类的一个实例,每个这样的实例用来表示一个Java类,我们可以根据class的实例得到该类的信息,并通过实例的newInstance()
方法创建出该类的一个对象,除此之外,ClassLoader
还负责加载Java应用所需的资源,如图像文件和配置文件等。
ClassLoader
类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的”类文件”。ClassLoader
类使用委托模型来搜索类和资源。每个ClassLoader
实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader
实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。
JVM平台提供三层classLoader
Bootstrap classLoader
采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.*
、java.util.*
等;这些类位于%JAVA_HOME%/jre/lib/rt.jar
。Bootstrap ClassLoader
不继承自ClassLoader
,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,BootStrap ClassLoader
也随着启动,负责加载完核心类库后,并构造Extension ClassLoader
和App ClassLoader
类加载器。
ExtClassLoader
扩展的class loader
, 加载位于%JAVA_HOME%/jre/lib/ext
目录下的扩展jar
。
AppClassLoader
系统class loader
, 父类是ExtClassLoader
,加载CLASSPATH
下的目录和jar
;它负责加载应用程序主函数类。
其体系结构如下:
如果要实现自己的类加载器,不管是实现抽象列ClassLoader
,还是继承URLClassLoader
类,它的父加载器都是AppClassLoader
,因为不管调用哪个父类加载器,创建的对象都必须最终调用getSystemClassLoader()
作为父加载器,getSystemClassLoader()
方法获取到的正是AppClassLoader
。
注:
Bootstrap classLoader
并不属于JVM的等级层次,它不遵守ClassLoader
的加载规则,Bootstrap classLoader
并没有子类。
加载顺序
1、Bootstrap CLassloder
2、Extention ClassLoader
3、AppClassLoader
JVM加载class文件到内存有两种方式
隐式加载
不通过在代码里调用ClassLoader
来加载需要的类,而是通过JVM来自动加载需要的类到内存,例如:当类中继承或者引用某个类时,JVM在解析当前这个类不在内存中时,就会自动将这些类加载到内存中。
显式加载
在代码中通过CLassLoader
类来加载一个类,例如调用this.getClass.getClassLoader().loadClass()
或者Class.for
ClassLoader加载类的过程
1、找到.class
文件并把这个文件加载到内存中。
2、字节码验证,Class
类数据结构分析,内存分配和符号表的链接。
3、类中静态属性和初始化赋值以及静态代码块的执行。
自定义类加载器
为何要自定义类加载器
JVM提供的类加载器,只能加载指定目录的jar
和class
,如果我们想加载其他位置的类或者jar
时,例如加载网络上的一个class
文件,默认的ClassLoader
就不能满足我们的需求了,所以需要定义自己的类加载器。
如何实现自定义的类加载器
我们实现一个ClassLoader
,并指定这个ClassLoader
的加载路径。有两种方式:
方式一
继承CLassLoader
,重写父类的findClass()
方法,代码如下:
|
|
在第13行,我们调用了父类的
loadClass()
方法,该方法使用指定的二进制名称来加载类,下面是loadClass
方法的源代码:
|
|
这个方法首先检查指定
class
是否已经被加载,如果已被加载过,则调用resolveClass()
方法链接指定的类,如果还未加载,则先将搜索类或资源的任务委托给其他父类加载器,检查该class
是否被BootstrapClassLoader
加载,如果上述两部均没有找到加载的class
,则调用findClass()
方法,在我们自定义的加载器中,我们重写了findClass
方法,去我们指定的路径下加载class
文件。另外,我们自定义的类加载器没有指定父加载器,在JVM规范中不指定父类加载器的情况下,默认采用系统类加载器即
AppClassLoader
作为其父加载器,所以在使用该自定义类加载器时,需要加载的类不能在类路径中,否则的话根据双亲委派模型的原则,待加载的类会由类加载器加载。如果一定想要把自定义加载器需要加载的类放在类路径中,就要把自定义类加载器的父加载器设置为null。
方式二
继承URLClassLoader
类,然后设置自定义路径的URL来加载URL下的类。
我们将指定的目录转换为URL路径,然后重写findClass
方法。
实现类的热部署
什么是类的热部署
所谓热部署,就是在应用正在运行的时候升级软件,不需要重新启用应用。
对于Java应用程序来说,热部署就是运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。
类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序。
如何实现Java类的热部署
前面的分析,我们已经知道,JVM在加载类之前会检查请求的类是否已经被加载过来,也就是要调用findLoadedClass方法查看是否能够返回类实例。如果类已经加载过来,再调用loadClass会导致类冲突。
但是,JVM判断一个类是否是同一个类有两个条件:一是看这个类的完整类名是否一样(包括包名),二是看加载这个类的ClassLoader加载器是否是同一个(既是是同一个ClassLoader类的两个实例,加载同一个类也会不一样)。
所以,要实现类的热部署可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名的类。