Java类加载机制

1、什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在java堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class 对象。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
2、 类的加载过程
JVM 将类的加载过程分为三个大的步骤:加载(loading),链接(link),初始化(initialize)。其中链接又分为三个步骤:验证,准备,解析。
(1) 加载:查找并加载类的二进制数据
加载是类加载过程中的第一个阶段,加载过程虚拟机需要完成以下三件事情:
1) 通过一个类的全限定名来获取其定义的二进制字节流;
2) 将这个字节流所代表的静态存储结构转为方法区的运行时数据结构;
3) 在Java 堆中生成一个代表这个类的java.lang.Class 对象,作为方法区中这些数据的访问入口。
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据
(2) 链接:
① 验证:确保被加载类的正确性;
主要是为了安全考虑,为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
② 准备:为类的静态变量分配内存,并将其初始化为默认值;
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
1)、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
2)、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
假设一个类变量的定义为:public static int value = 3;
那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器
③ 解析:把类中的符号引用转换为直接引用;
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
(3) 初始化:为类的静态变量赋予正确的初始值
为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
① 声明类变量是指定初始值;
② 使用静态代码块为类变量指定初始值;
③ JVM初始化步骤
1)、假如这个类还没有被加载和连接,则程序先加载并连接该类
2)、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3)、假如类中有初始化语句,则系统依次执行这些初始化语句
④ 类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
– 创建类的实例,也就是new的方式
– 访问某个类或接口的静态变量,或者对该静态变量赋值
– 调用类的静态方法
– 反射(如Class.forName(“com.shengsiyuan.Test”))
– 初始化某个类的子类,则其父类也会被初始化
– Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
(4) 结束生命周期
在如下几种情况下,Java虚拟机将结束生命周期
– 执行了System.exit()方法
– 程序正常执行结束
– 程序在执行过程中遇到了异常或错误而异常终止
– 由于操作系统出现错误而导致Java虚拟机进程终止
3、类加载器
JVM 类加载器作用,将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
类加载器是通过ClassLoader 及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

(1) Bootstrap ClassLoader 引导类加载器
负责加载Java核心库$JAVA_HOME中的jre/lib/rt.jar 里所有的class,由c++实现,不是ClassLoader子类。
(2) Extension ClassLoader 扩展类加载器
负责加载Java 平台中扩展功能的一些jar包,包括$JAVA_HOME中的jre/lib/ext/*.jar 或 -D java.ext.dirs指定目录下的jar包。
(3) App ClassLoader
负责加载classpath 中指定的jar包及目录中class
(4) Custom ClassLoader
应用程序根据自身需要自定义的ClassLoader,如tomcat,jboss 都会根据j2ee规范自行实现ClassLoader,加载过程中会先检查是否已被加载,检查顺序是自底向上,从Custom ClassLoader 到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类在所有ClassLoader 只加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
4、JVM 三种预定义加载器
JVM预定义有三种类加载器,当一个 JVM启动的时候,Java 默认开始使用如下三种类加载器:
(1) 引导类加载器(Bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。它负责将
(2) 扩展类加载器(Extensions class loader):该类加载器在此目录里面查找并加载 Java 类。扩展类加载器是由Sun的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dirs指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
(3) 系统类加载器(System class loader):系统类加载器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
5、类加载器 “双亲委派” 机制
(1) 双亲委派机制介绍
在这里需要着重说明,JVM在加载类时默认采用的是双亲委派机制。所谓的双亲委派机制,就是某个特定的类加载器在接到类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以从系统类加载器和标准扩展类加载器为例作简单分析。
双亲委派机制是为了保证Java核心库的类型安全。**这种机制能保证不会出现用户自己能定义java.lang.Object类的情况,因为即使定义了,也加载不了。**

