一.类加载器
1.概述:
- 类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 Web 容器和 OSGi 中得到了广泛的使用。一般来说,Java 应用的开发人员不需要直接同类加载器进行交互。Java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时间去调试
ClassNotFoundException
和NoClassDefFoundError
等异常。 - 基本概念:当需要用到某个类时,虚拟机将会加载类的二进制文件,并在内存中创建对应的class对象,这个过程称之为类的加载
2.整个过程示意图:
- 磁盘上存放:.java文件以及经过编译的.class文件
- 将磁盘上的.class文件加载进内存,然后连接,初始化生成 Class对象
- 在内存中有方法区和堆区,在堆区的Class对象会引用方法区的二进制数据文件。
3.详细讲解加载,连接和初始化
- 加载:
- 通过类的全限定名来获取定义此类的二进制字节流
- 将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构
- 在内存中生成代表此类的java.lang.Class对象,作为该类访问入口.
- 连接:分为三个阶段
- 验证:连接阶段第一步.验证的目的是确保Class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:
- 文件格式验证
- 源数据验证
- 字节码验证
- 符号引用验证
- 准备:连接阶段第二步,正式为类变量分配内存并设置变量的初始值.(仅包含类变量,不包含实例变量)。
- 解析:连接阶段第三步,虚拟机将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口,字段,类方法,方法类型等等。
- 验证:连接阶段第一步.验证的目的是确保Class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:
- 初始化:开始执行类中定义的java程序代码,执行类构造器。
4.类加载器分类
(1)类加载器分为两种:
- Java虚拟机自带的类加载器(3种)
- 启动类加载器:Bootstrap ClassLoader,又名根类加载器或引导类加载器
- 扩展类加载器:Extendsion ClassLoader
- 系统类加载器:Application ClassLoader,又名应用类加载器
- 用户自定义的类加载器:一般是java.lang.ClassLoader的子类实例。
(2)Bootstrap ClassLoader:根类加载器
概念:最底层的类加载器,是虚拟机的一部分,由C++语言实现且没有父类加载器,也没有继承java.lang.ClassLoader类。它主要负责加载系统属性"sun.boot.class.path"指定的路径下的核心类库(jre\lib),出于安全考虑,根类加载器只加载java、javax、sun开头的类
注:由于Object类的类加载器是根类加载器,所以打印出来是null
1 public class ClassLoaderDemo1 { 2 3 public static void main(String[] args) { 4 ClassLoader classLoader = Object.class.getClassLoader(); 5 System.out.println("Object类的类加载器是:" + classLoader);//Object类的类加载器是:null 6 } 7 }
(3)Extendsion ClassLoader:扩展类加载器
概念:有原SUN公司实现的类(JDK8和JDK9实现的类名不同),是由Java语言编写,父类加载器是根类加载器。负责加载jre\lib\ext目录下的类库或者系统变量“java.ext.dirs"指定的目录下的类库。
1 public class ClassLoaderDemo1 { 2 3 public static void main(String[] args) { 4 ClassLoader classLoader = DNSNameService.class.getClassLoader(); 5 System.out.println("DNSNameService类的类加载器是:" + classLoader);//DNSNameService类的类加载器是:sun.misc.Launcher$ExtClassLoader@7ea987ac 6 } 7 }
(4)Application ClassLoader:系统类加载器
概念:有原SUN公司实现的类(JDK8和JDK9实现的类名不同),是由Java语言编写,父类加载器是扩展类加载器。负责加载从classpath环境变量或者系统属性java.class.path所指定的目录中加载类。它是用户自定义类加载器的默认父类加载器。一般情况下,该类加载器是程序中默认的类加载器,可以通过ClassLoader.getSystemClassLoader()直接获得。
1 public class ClassLoaderDemo1 { 2 3 public static void main(String[] args) { 4 ClassLoader classLoader = ClassLoaderDemo1.class.getClassLoader(); 5 System.out.println("ClassLoaderDemo1类的类加载器是:" + classLoader);//ClassLoaderDemo1类的类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2 6 } 7 }
(5)小结:
在程序开发中,Java虚拟机对class文件采用的是按需加载的方式,需要该类时才会将该类的class文件加载进内存生成class对象,加载时采用双亲委派模式,即把加载类的请求交由父类加载器处理,它是一种任务委派模式。
类加载器树状组织结构示意图:
5.类加载器的双亲委派机制
委派机制图:
测试:
由于扩展类加载器的父类加载器为根类加载器是C++实现的,则获得到的classLoader=null,所以只显示两个类加载器
1 public class ClassLoaderDemo1 { 2 3 public static void main(String[] args) { 4 ClassLoader classLoader = ClassLoaderDemo1.class.getClassLoader(); 5 6 while (classLoader != null){ 7 System.out.println(classLoader); 8 classLoader = classLoader.getParent();//获取父类加载器 9 } 10 11 /**结果: 12 * sun.misc.Launcher$AppClassLoader@18b4aac2 13 * sun.misc.Launcher$ExtClassLoader@6d6f6e28 14 * 15 */ 16 } 17 }
注:自定义的类包名不要是java、javax、sun开头的类,这样获得的类加载器会报错
6.ClassLoader
- 概念:所有的类加载器(除了根类加载器)都必须继承java.lang.ClassLoader,它是一个抽象类。
- 主要方法:
- protected Class<?> loadClass(String name, boolean resolve) :加载Class对象
- 注意:不要覆盖该方法
- 步骤:
- 通过二进制名称调用 findLoadedClass 方法检查是否已经加载该类
- 没有加载则在父类加载器上调用loadClass方法,如果父类加载器为null,则使用根类加载器
- 调用 findClass 方法查找该类
- protected Class<?> findClass(String name) :在自定义类时我们需要覆盖这个类
-
:用byte字节解析成虚拟机能够是别的Class对象。
- 通常与findClass方法一起使用。
- 在自定义类加载器是,会直接覆盖ClassLoader的findClass方法获取需要加载的类的自己吗,最后调用defindClass方法生成Class对象。
- protected final void resolveClass(Class<?> c) :指定连接的类。类加载器可以使用此方法来连接类。
- protected Class<?> loadClass(String name, boolean resolve) :加载Class对象