我们知道Java虚拟机执行的是编译后的字节码文件,那么Class文件是怎么加载,并且在虚拟机中执行的呢?
虚拟机类加载机制分为三步
加载—>连接—>初始化
加载
- 通过类的全限定名来获取定义此类的二进制流
- 将这个自己流所代表的静态存储结构化位运行时数据区中的数据结构
- 在内存中生成一个代表此类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证
- 文件格式验证:对字节流的校验
Class文件是否以0xCAFFEEBABE开头
主,次版本号是否在当前虚拟机处理范围之内
常量池中是否由不被支持的常量类型
….. - 元数据验证:对类的元数据语法,语义的校验
这个类是否由父类
这个类的父类是否继承了不被允许继承的类
如果这个类不是抽象类,是否实现其父类或者接口中的所有方法 - 字节码验证:
运行检查
栈数据类型和操作码数据参数的吻合
跳转指令到合理的位置 - 符号引用验证:将符号引用转化位直接引用
符号引用中通过字符串描述的类的全限定名是否能找到相应的类
访问的方法或者字段是否由足够的权限
准备 ##
为类正式分配内存并设置类变量的初始值,这些变量所使用的内存都是在方法区中分配的
这里只为类变量分配内存,实例变量内存的分配实在堆中分配的
public static int a = 3;
注意:上述语句中,在准备阶段,a的值为0,而不是3,将3赋值给a实在初始化阶段
public static final int a = 3;
注意:上述语句中,因为定义的为常量,所以在准备阶段,就将a的值扶植为3,以后不允许修改
解析
此阶段,是将符号引用转化为直接引用
初始化
初始化是类加载过程的最后一步,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作都由虚拟机主导和控制,到了初始化阶段,才开始真正执行类中定义的Java程序代码
- 从另外一个角度看,初始化过程是执行类构造器
<clinit>
方法的过程 - 执行此过程会执行 1.static变量赋值 2.static{}静态块语句
- 而且子类的
<clinit>
调用前保证父类的<clinit>
方法调用 <clinit>
是线程安全的
类加载器
在Java虚拟机中,由四种类加载器,他们分别用来加载不同的类,或者有着不同的职责
ClassLoader是一个抽象类
ClassLoader的实例将读入Java字节码将类装载到JVM中
ClassLoader可以定制,满足不同的字节码流获取方式
ClassLoader负责类装载过程中的加载阶段
BootStrap ClassLoader:
启动类加载器,这个加载器,在HotSpot虚拟机中用C++语言实现
将放在%JAVA_HOME%\lib目录中,或者被-Xbootclasspath参数指定路径中的,通常为\lib\目录下的rt.jar
Extension ClassLoader
扩展类加载器,此类加载器用于加载%JAVA_HOME%\lib\ext下的类库
Application ClassLoader
顾名思义,用户加载用户程序的类加载器,也被称为系统类加载器
Customer ClassLoader
用户自定义的类加载器
双亲委派模型
双亲委派模型要求除了BootStrap没有父类加载器之外,其他类加载器都必须要有一个父类加载器,Jvm规范中不强制使用双亲委派去加载类
好处:java类随着它的加载器一起具备了一种带有优先级的层次关系.