类的加载机制

204 阅读5分钟

类的生命周期

类的加载过程包括了 加载验证准备解析初始化五个阶段,用图描述如下

image.png

而在这五个阶段中,加载验证准备初始化 四个步骤发生的顺序是确定的,而解析阶段不一定,在某些情况下会发生在初始化阶段之后。

加载

类的加载,是类的加载过程中的第一步 在这个阶段虚拟机会做以下几件事情

1、通过类的全限定名获取二进制字节流
2、将该二进制流的静态存储结构转为方法区的运行是数据结构
3、在堆中生成该类的class对象\

加载过程完成后,虚拟机外部的二进制字节流会按照虚拟机所需的格式存储在方法区之中,而且Java堆中会创建一个java.lang.Class类的对象,这样就可以通过该对象访问方法区中的这些数据

加载.class文件的方式

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载
  • 将java源文件动态编译为class文件
  • 从专有数据库中提取.class文件

验证

验证的作用主要在于确保class文件的字节流符合虚拟机的要求,并且不会对虚拟机自身安全造成危害,验证阶段会完成4个检验动作

  • 文件格式验证:验证字节流是否符合class文件格式的规范
  • 元数据验证:对字节码描述的信息语音进行分析,确保符合java语音规范的要求
  • 字节码验证:通过对数据流和控制流分析,确定程序语义是合法的、符合逻辑的
  • 符号引用验证:确保解析动作能正确执行

准备

准备阶段是正式的为类变量分配内存并设置变量初始值的阶段,这些内存都将在方法区中分配
但是对于该阶段有几点需要注意:

  • 这里的内存分配仅包括类变量static,而不包括实例变量,实例变量会在对象实例化的时候随着对象一起分配在java堆中
  • 这里设置的初始值通常情况下是数据类型默认的值,而不是被在java代码中显式的赋予的值
  • 对于基本数据类型、类变量(static)、全局变量,如果不显式的对其赋值,系统会为其赋予默认值及零值,而对于局部变量来说,在使用前不许显式的赋值,否则编译不通过
  • 同时被staticfinal修饰的常量,必须在声明的时候就显式的赋值,否则编译不通过,但是如果只被final修饰的常量则可以既在声明时显式的为其赋值,也可以在类初始化的时候为其显式赋值。
  • 对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其显式的赋值,系统会赋予默认的null值
  • 如果数组初始化时没有对数组中的各元素赋值,那么系统会根据元素对应的数据类型赋予默认的零值

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄

初始化

初始化是为类的静态变量赋予正确的初始值,初始化阶段会调用类的构造器

JVM初始化步骤

  • 假如这个类还没有被加载和连接,则程序先加载并连接该类
  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类
  • 假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机: 只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • 创建类的实例,也就是new的方式
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如Class.forName("com.pdai.jvm.Test"))
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

类加载器

类的加载器是指:通过一个类的全限定类名获取该类的二进制字节流叫做类加载器,类加载器分为四类:

  • 启动类加载器(Bootstrap ClassLoader):用来加载java核心类库,无法被java程序直接引用
  • 扩展类加载器(Extension ClassLoader):用来加载java的扩展库,java的虚拟机实现会提供一个扩展库目录,该类加载器在扩展库目录里面查找并加载java类
  • 系统类加载器(Application ClassLoader):它根据java的类路径来加载类,一般来说,java应用的类都是通过它来加载
  • 自定义类加载器:继承自ClassLoader

image.png

类的加载

类的加载有三种方式:
1、命令行启动应用时候由jvm初始化加载
2、通过Class.forName()方法动态加载
3、通过ClassLoader.loadClass()方法加载

双亲委派机制

什么是双亲委派机制?
当一个类加载器接收到一个类的加载请求,它首先不会尝试自己去加载,而是将这个请求委派给父类的加载器去加载,只有父类的加载器在搜索范围内查找不到该类,子加载器才会去尝试自己加载该类

为什么使用双亲委派机制?
双亲委派机制解决的问题是确保每个类的全限定名在虚拟机中保证唯一性,举个例子 如果用户在自己的项目中定义了一个java.lang.String类,那么当我们去使用String这个类的时候 就无法确定是使用那个String了,也就无法保证类的唯一性

双亲委派的优势

  • 防止系统中出现多份相同的字节码,保证类的唯一性
  • 保证java程序安全稳定的进行