浅谈类加载机制

类加载

概念:

当JVM第一次使用一个类的时候,通过classPath(类路径)找到对应的 .class 文件,从 .class文件中读取该类的信息

(包、类名、父类、属性、成员方法、构造方法等),读取之后将信息存储在JVM内存中,一个类只进行一次类加载。

浅谈类加载机制

类的生命周期:

浅谈类加载机制

类加载过程:

浅谈类加载机制

加载到初始化都是在程序的与运行期间完成的。验证,准备,解析也叫连接过程,Java的特性是依赖在运行期动态加载和动态连接。

加载

”加载“是”类加机制”的第一个过程,在加载阶段,虚拟机主要完成三件事:

(1)通过一个类的全限定名来获取其定义的二进制字节流

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

(3)在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问入口。

连接-验证

目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身的安全,

根据2011年发布的<java虚拟机规范>的要求,验证阶段大致需要一下四个阶段来验证

主要包括四种验证:文件格式的验证,元数据的验证,字节码验证,符号引用验证。

  • 文件格式验证,该阶段主要在字节流转化为方法区中的运行时数据时,负责检查字节流是否符合Class文件的规范,保证其可以正确的被解析并存储于方法区中。
  • 元数据验证,确保Class的语义描述符合Java的Class规范。如:该Class是否有父类、是否错误继承了final类、是否一个合法的抽象类等。
  • 字节码验证,通过分析数据流和控制流,确保程序语义符合逻辑。如:验证类型转换是合法的。
  • 符号引用验证,发生于符号引用转换为直接引用的时候(转换发生在解析阶段)。如:验证引用的类、成员变量、方法的是否可以被访问,当前类是否存在相应的方法、成员等。

连接-准备

在准备阶段,虚拟机会在方法区中为Class分配内存,并设置static成员变量的初始值为默认值。注意这里仅仅会为static变量分配内存(static变量在方法区中),并且初始化static变量的值为其所属类型的默认值。如:int类型初始化为0,引用类型初始化为null。即使声明了这样一个static变量:

public static int a = 123;

在准备阶段后,a在内存中的值仍然是0, 赋值123这个操作会在中初始化阶段执行,因此在初始化阶段产生了对应的Class对象之后a的值才是123 。为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值,(如static int i = 5 这里只是将 i 赋值为0,在初始化的阶段再把 i 赋值为5),这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。

连接-解析

常量(用final修饰的成员变量表示常量,值一旦给定就无法改变!)

解析阶段,虚拟机会将常量池中的符号引用替换为直接引用,解析主要针对的是类、接口、方法、成员变量等符号引用。在转换成直接引用后,会触发校验阶段的符号引用验证,验证转换之后的直接引用是否能找到对应的类、方法、成员变量等。

初始化

初始化阶段,才真正开始执行类中定义的java程序代码

这里是类记载的最后阶段,如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值,成员变量也将被初始化)

类加载时机:

①第一次创建该类对象:先进行类加载,在完成对象的创建

②第一次使用该类的静态成员(静态属性和静态方法)

③子类的类加载会导致其父类先进行类加载

④使用java.lang.reflect包的方法对类进行反射调用的时候

⑤当虚拟机启动时,用户指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

双亲委派机制:

浅谈类加载机制

首先需要知道的是,java类加载遵循一个机制——Parents Delegation Model(双亲委派模型)
双亲委派机制:就是某个特定的类加载器在接收到加载类的请求时,除非显示的要求使用某一个类加载器,都会将加载任务委托给父类加载器,父类加载器又将加载任务继续向上委托,知道最终父类加载器,如果最终父类加载器可以完成此类的加载任务,就由其完成加载,如果不行就依次向下传递任务,由其子类加载器进行加载。

双亲委派机制有什么好处?

1、 出于安全考虑,这么做保证了java核心库的安全性,确保基础类永远都是由java提供的跟加载器来加载。
2、 可以避免重复加载,当父加载器已经加载了该类后,子类就没有必要再加载一次。
从以上两点出发,如果有人恶意篡改了基础类的代码(例如:java.lang.string)那他自己定义的java.lang.string将永远不会被加载进来,因为原始的String类已经在启动的时候就被加载进来了。