一、类加载概念

类加载就是将编译好的.class文件由类加载器动态加载到JVM中,最终形成可以被JVM使用的Java类型。被加载的类会放在JVM的运行时数据区的方法区内,然后在java堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,并提供访问入口

二、类加载的过程

JVM 将类的加载过程分为三个大的步骤:加载(loading),链接(link),初始化(initialize)。其中链接又分为三个步骤:验证,准备,解析。

java 类加载器模型 java类加载器工作机制_Java

1.加载

加载又可以称为装载,是类加载过程中的第一个阶段,这个阶段过程中虚拟机需要完成三件事情

(1):获取二进制字节流

(2):将字节流所代表的静态存储结构转为方法区运行时数据结构

(3):在Java堆中生成一个代表这个类的java.lang.class对象,作为方法区的访问入口

加载类的途径有多种

  • 从本地文件系统加载class文件
  • 从Jar包中加载class文件
  • 通过网络加载class文件
  • 将源文件编译成class文件
2.验证

这一步骤主要是保证被加载类的合法性,主要是为了安全考虑,确保Class类符合虚拟机要求,能够被虚拟机正确解析

  • 验证Class文件的标识:这个标识名词叫做魔数(Magic Number),是由Java定义的,到JDK1.8为止,为cafebabe
  • 验证Class文件的版本号,主要是用来区分不同版本Java编译的Class文件
  • 验证常量池(常量类型以及数据结构是否正确)
  • class文件的每个部分(字段表、方法表是否正确)
  • 元数据验证(父类验证、继承验证、final验证)
  • 字节码验证(指令是否正确)
  • 符号引用验证(通过符号引用是否能找到字段、方法、类)

补充:验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间

3.准备

为类的静态变量(类变量)分配内存,并将其初始化为默认值

  • 这个时候进行内存分配的仅仅是类变量(static),不包括实例变量,实例变量会在对象实例化时随着对象一块在Java堆中分配
  • 这里赋予默认值(初始值)通常是数据类型的默认值(如0,null,false)等,而不是显式的赋值
4.解析

把类中的符号引用转换成直接引用

  • 解析动作主要针对于:类、接口、字段、类方法、接口方法、方法类型等
  • 符号引用指的就是定位目标的一种描述符号,如String s="abc"中的s就是符号引用
  • 直接引用:直接指向目标的指针、相对偏移量、一个间接定位到对象的句柄,直接引用的目标一定是被加载到了内存中
5.初始化

这个步骤为类的静态变量赋予正确的初始值,JVM负责对类进行初始化。

  • 有两种初始化方式:
    ① 声明类变量时指定初始值(如static int a=5)
    ②使用静态代码块为类变量指定初始值
  • JVM初始化步骤:
    ① 假如这个类还没有被加载和连接,则程序先加载并连接该类
    ② 假如该类的直接父类还没有被初始化,则先初始化其直接父类
    ③ 假如类中有初始化语句,则系统依次执行这些初始化语句
  • 类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,有6中情况
    ① 创建该类的实例,即new对象
    ② 访问类或接口的静态变量,或者对该静态变量进行赋值
    ③ 调用类的静态方法
    ④ 使用反射(class.forName(“xxx”))
    ⑤ 初始化子类时,其父类也会被初始化
    ⑥ Java虚拟机启动时被表明为启动类的类(Java Test),直接使用java.exe运行某个主类时
6.使用和卸载

当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码,当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。

有如下情况,Java虚拟机会结束其生命周期

  • 执行了System.exit()方法
  • 程序正常执行结束
  • 程序在执行过程中出现异常或错误
  • 操作系统出现错误导致Java虚拟机进程终止

三、类加载器

1.概述
  • Java类加载器(Java Classloader)是Java运行时环境的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。某些情况下也可预先加载。
  • 可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求而不需要完全了解Java虚拟机的类加载的细节。
2.加载器类型

JVM中有3个默认的类加载器

类型

说明

引导类加载器(Bootstrap Classloader)

由原生代码(如C语言)编写,不继承自java.lang.ClassLoader,负责加载核心类库,存储在<JAVA_HOME>/jre/lib目录中

扩展类加载器(Extensions Classloader)

它负责将 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中,该类由sun.misc.Launcher$ExtClassLoader实现

系统类加载器(AppClassloader)

根据 Java应用程序的类路径(java.class.path或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现。

(其实除了以上三种加载器以外,还有一种比较特殊的类型就是线程上下文类加载器)

3.双亲委派加载机制

类加载器在加载某个类时,采用的是双亲委派机制。

java 类加载器模型 java类加载器工作机制_类加载器_02

双亲委派示意图

(1)双亲委派工作原理:

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。

(2)双亲委派机制的好处:

用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。