类加载机制
在了解类加载之前我们先来了解一下我们所写的Java代码如何进行工作,我们编写的.java文件经过javac编译后形成.class文件,.class文件再被加载到内存中进行运行。下面先来看看.class(类)文件中都有哪些内容。
.class(类)文件的结构
在类文件开头首先会有4个字节的魔数,它的功能主要是用来确定该文件是否能够被JVM接受。接下来会有一系列的版本信息,包括第5、6个字节代表的次版本号,第7、8个字节代表的主版本号。接下来分别是常量池表、访问标志、索引(包括类索引、父索引、接口索引)集合、字段表集合、方法表集合、属性表集合。
类的生命周期
当遇到一下四种情况是必须立刻对类进行初始化操作:
- 通过new关键字实例化类的对象、调用类的静态方法、get/set类的静态变量;
- 使用反射机制对类进行反射调用时;
- 当初始化一个类时如果其父类未被初始化,需要先初始化其父类
- 虚拟机启动时,主类(即包含main方法的类)需要被初始化
类的加载过程
类的加载过程也就是上图中的加载、验证、准备、连接、初始化五个步骤。
加载
加载阶段(注意此处的加载是类加载过程中的一个步骤,而不是只类加载)主要完成三个任务
- 根据类的权限定名获取指定的二进制流;
- 将类文件中的静态存储结构转换为方法区中的运行时存储结构;
- 在堆中创建代表该类对应的java.lang.Class对象。
验证
验证阶段主要是为了确保输入的类文件的字节流满足JVM的要求,避免其对JVM造成损害。虽然编译器会帮JVM拒绝一些不安全的代码,比如数组越界、对象类型的错误转换等,但是输入的字节流并不一定都是经过编译得来的,所以存在一定威胁。验证阶段主要包括下面四个过程
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
准备阶段会对类变量进行内存分配并设置初始值(这里需要注意的是,在准备阶段设置的初始值并非代码中设置的初始值,二是系统默认的零值,这一点用过C++的或许较为熟悉,变量没有赋值时系统会给它自动设置一个初始值)。
解析
解析阶段主要是将常量池中的符号引用转化为直接引用。这里来简单说一下符号引用和直接引用,符号引用就是指用一个符号来指定引用的目标,只要在使用时能够保证无歧义的找到即可(可以理解为符号引用是我去找我的导师,但我并不关心他在哪,当我真正去找的时候我才需要知道他在哪个办公室)。直接引用可以使直接指向引用目标的指针、句柄或者相对偏移量。(此时我就需要知道导师办公室在哪个楼,门牌号是多少了)。
类加载器
类加载器可以分为:启动类加载器、扩展类加载器、应用类加载器以及用户自定义的类加载器
这里提一下类的双亲委派机制,即当一个类需要被加载时,不会立刻调用该类的加载器,而是向上找其父类加载器,知道找到最顶部的启动类加载器;如果父类不能够完成加载任务在向下依次找子类加载器完成加载。
’