Java类加载机制
类加载过程就是虚拟机将描述类的数据从.class文件加载进内存的方法区(jdk1.8之后叫做元数据区),并对数据进行校验,创建class对象并进行解析、初始化类变量(静态变量)的过程。
我们编写的java程序本身就是一个类文件,当执行java代码时,首先要加载main函数所在的类(这种情况,当没有new该类时,则不会初始化该类的实例变量(非静态)也不会执行类的构造方法)。
加载:虚拟机根据全限定类名来获取定义此类的二进制字节流,并在堆内存中创建class对象,class对象为访问方法区中的类数据提供了接口。此时并未将数据加载进内存,需要等验证阶段通过,才会在内存中为类数据分配空间。并且该class对象并未进行初始化,即没有执行构造函数。
验证:连接阶段的第一步,保证class文件的字节流符合JVM规范,不会导致JVM出现安全问题。
- 文件格式验证:如是否以魔数 0xCAFEBABE 开头、主、次版本号是否在当前虚拟机处理范围之内、常量合理性验证等。保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java类型信息的要求。
- 元数据验证:是否存在父类,父类的继承链是否正确,抽象类是否实现了其父类或接口之中要求实现的所有方法,字段、方法是否与父类产生矛盾等。保证不存在不符合 Java 语言规范的元数据信息。
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。例如保证跳转指令不会跳转到方法体以外的字节码指令上。
- 符号引用验证:在解析阶段中发生,保证可以将符号引用转化为直接引用。
准备:为类变量在方法区中分配一定的内存,并赋默认值,即不同类型的零值。
解析:将class常量池的符号引用转变为直接引用。此处主要是静态、私有方法及属性,这是由于这类方法不能被重写,属性在运行期不可变。
通俗地说,符号引用是需要依据几层关系的链接最终找到数据,而直接引用是引用直接指向数据。比如a指向b,b指向c,c指向10,这里a是符号引用,c是直接引用直接引用则是a直接指向10。
初始化:为类变量赋初值,执行静态代码块。一个类在程序运行过程中只需要加载一次即可,而类变量初始化和执行静态代码块发生在类加载过程,所以类变量(静态变量)只会初始化一次,静态代码块也只会执行一次。
<clinit>()是类构造器,<init>()是实例构造器,初始化阶段,执行<clinit>()方法初始化类变量,类加载完成之后,如果需要初始化对象(比如new一个对象)会调用<init>()方法来初始化对象。
类加载时机
当然一个java程序必然牵涉很多个类,这些类不会在一开始全部加载到内存,只有使用到某个类,才会加载。虚拟机规定了以下情况需要对类进行“初始化”(在此之前完成了加载、验证、准备、解析)
- 运行main函数,main函数所在的类会被加载(这种情况,类对象不会被初始化)
- 使用new实例化对象、调用类的静态方法、访问类或接口的类变量
- 对类进行反射调用
- 当初始化类的父类还没有进行初始化,则需要先触发其父类的初始化
类加载器
类加载器是指:实现"通过一个类的全限定性类名获取该类的二进制字节流"这个动作的代码模块叫做类加载器;类加载器分为以下四种:
- 启动类加载器(BootStrap ClassLoader):用来加载java核心类库(jre/lib/rt.jar);
- 扩展类加载器(Extension ClassLoader) :用来加载java的扩展库(jre/ext/*.jar),java的虚拟机实现会提供一个扩展库目录,该类加载器在扩展库目录里面查找并加载java类;
- 系统类加载器(Application ClassLoader):它根据java的类路径来加载类,一般来说,java应用的类都是通过它来加载的;
- 自定义类加载器:由java语言实现,继承自ClassLoader;
双亲委派机制
当某个特定的类加载器(ClassLoader,每个类都有一个)在收到类加载的请求时,首先判断被加载的类是否已经加载过,如果是则结束,否则会将加载任务委托给自己的父亲;父类加载器在收到类加载的请求时,也会先判断被加载的类是否已经加载过;以此类推,直到将加载任务委托给Bootstrap ClassLoader为止。此时,Bootstrap ClassLoader先判断是否加载过,然后判断能否直接加载,如果不能则将加载任务交给儿子,同样,若加载任务给到最底层加载器且依旧无法加载,则抛出一个异常:ClassNotFoundException。
作用:保证了Java核心库的安全性,如果我们自定义了一个java.lang.String类,那么JVM会按照顺序加载JDK自带的String类。在Java中一个类具有相同的类名并且由同一个类加载器加载才被认为是同一个类。如果不同的类具有相同的类名,且都有一个类加载器,这样使得一个类名对应不同的类,将会扰乱代码秩序。
参考博客: