目录
ClassLoader发展过程
类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。
现在类加载器在 Web 容器和 OSGi 中得到了广泛的使用。一般来说,Java 应用的开发人员不需要直接同类加载器进行交互。Java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况。
ClassLoader概念
classLoader是类加载器,是负责加载类的类,用来动态加载Java 类(class文件)到 Java 虚拟机中并执行。但是,虚拟机启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载,因为一次全加载,那内存很容易崩溃。
JVM设计者把类加载阶段中的“通过‘类全名’来获取定义此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
运行Java程序,就是执行java这个命令,指定包含main方法的完整类名,以及一个classpath,即类路径。类路径可以有多个,对于直接的class文件,路径是class文件的根目录,对于jar包,路径是jar包的完整名称(包括路径和jar包名)。Java运行时,会根据类的完全限定名寻找并加载类,寻找的方式基本就是在系统类和指定的类路径中寻找。
自定义Class Loader可以实现的功能
1、热部署。在不重启Java程序的情况下,动态替换类的实现,比如Java Web开发中的JSP技术就利用自定义的ClassLoader实现修改JSP代码即生效。
2、应用的模块化和相互隔离。不同的ClassLoader可以加载相同的类但互相隔离、互不影响。Web应用服务器如Tomcat利用这一点在一个程序中管理多个Web应用程序,每个Web应用使用自己的ClassLoader,这些Web应用互不干扰。OSGI和Java 9利用这一点实现了一个动态模块化架构,每个模块有自己的ClassLoader,不同模块可以互不干扰。
3、从不同地方灵活加载。系统默认的ClassLoader一般从本地的.class文件或jar文件中加载字节码文件,通过自定义的ClassLoader,我们可以从共享的Web服务器、数据库、缓存服务器等其他地方加载字节码文件。
java类加载过程
Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。 基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。
JVM(虚拟机)把描述类的数据的字节码.Class文件加载到内存,并对数据进行校正、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括如下七个阶段:
1、加载(查找并加载类的二进制数据):
加载阶段是“类加载机制”中的一个阶段,这个阶段通常也被称作“装载”,主要完成如下工作:
(1)通过“类全名”来获取定义此类的二进制字节流
(2)将字节流所代表的静态存储结构转换为方法区的运行时数据结构
(3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
2、验证(确保被加载类的正确性):
验证是链接阶段的第一步,这一步主要目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
3、准备(为类的静态变量分配内存,并将其初始化为默认值)。
4、解析(把类中的符号引用转换为直接引用)。
5、初始化(为类的静态变量赋予正确的初始值)。
java类加载器的继承关系
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。
系统提供的类加载器:
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。负责加载 java环境变量JAVA_HOME\lib目录中并且能被虚拟机识别的类库到JVM内存中,如果名称不符合的类库即使放在lib目录中也不会被加载。该类加载器无法被Java程序直接引用。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该加载器主要是负责加载 java环境变量JAVA_HOME\lib\ext,该加载器都可以被开发者直接使用。
- 系统类加载器(App class loader 或者叫 system class loader):它根据 Java 应用的类路径(CLASSPATH,环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。这个加载器的实现类是sun.misc. Launcher$AppClassLoader。
启动类加载器(Bootstrap ClassLoader)使用C++语言实现,属于虚拟机自身的一部分,不是ClassLoader的子类。所有其他的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类lava.lang.ClassLoader。
java.lang.ClassLoader
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外, ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
类ClassLoader是一个抽象类,Application ClassLoader和ExtensionClassLoader的具体实现类分别是sun.misc.Launcher$AppClassLoader和sun.misc.Launcher$ExtClassLoader, Bootstrap ClassLoader不是由Java实现的,没有对应的类。
每个应用类Class对象都有一个方法,可以获取实际加载它的ClassLoader,方法是:
ClassLoader有一个方法,可以获取它的父ClassLoader:
举例:
ClassLoader有一个静态方法,可以获取默认的系统类加载器:
ClassLoader中有一个方法loadClass(),用于加载应用类:
举例:
java.util.ArrayList实际由BootStrap ClassLoader加载,所以返回值就是null。
Class的两个静态方法forName:
第一个方法使用系统类加载器加载,第二个方法指定ClassLoader,参数initialize表示加载后是否执行类的初始化代码(如static语句块),没有指定默认为true。
ClassLoader的loadClass方法与Class的forName方法都可以加载应用类,它们有什么不同呢?基本是一样的,不过,ClassLoader的loadClass不会执行类的初始化代码。
例子:
使用ClassLoader加载静态内部类Hello, Hello有一个static语句块,输出"hello",运行该程序,类被加载了,但没有任何输出,即static语句块没有被执行。如果将loadClass的语句换为:
则static语句块会被执行,屏幕将输出"hello"。
loadClass()的内部实现调用另一个loadClass,其中还有调用findClass。
findClass是一个protected方法,类ClassLoader的默认实现就是抛出ClassNotFoundException。
---------------------------------------------------------------------------------------------------------------------
参考文献:
https://blog.csdn.net/briblue/article/details/54973413
《Java编程的逻辑》第24章 类加载机制