在开发过程中,经常会遇到Java.lang.ClassNotFoundExcetpion这个异常,这就涉及了类加载机制。
类加载的基本概念
类的加载是指将类的.class文件读入到内存(方法区)中,然后在堆区创建一个Java.lang.Class 对象(Java反射机制)。类最终加载的是位于堆区的Class对象,其封装了类的数据结构。类的加载可以在类首次主动使用 时加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
加载.class文件的方式
– 从本地系统中直接加载
– 通过网络下载.class文件
– 从zip,jar等归档文件中加载.class文件
– 从专有数据库中提取.class文件
– 将Java源文件动态编译为.class文件
类加载过程
(1) 装载(加载):查找和导入Class文件;
(2) 连接:把类的二进制数据合并到JRE中;
(a)校验:检查载入Class文件数据的正确性;
(b)准备:给类的静态变量分配存储空间;
(c)解析:将符号引用转成直接引用;
(3) 初始化:对类的静态变量,静态代码块执行初始化操作
1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
准备阶段
是正式为类变量分配并设置类变量初始值的阶段,这些内存都将在方法区中进行分配,这时候进行内存分配的仅包括类变量(被static修饰的变量)。准备阶段后,类变量的初始值是0,而初始化之后才赋值。
初始化阶段初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
①声明类变量(static变量)是指定初始值
②使用静态代码块为类变量指定初始值。static块。
一个类中,一个static变量只会有一个内存空间,虽然有多个类实例,但这些类实例中的这个static变量会共享同一个内存空间。 final修饰的变量会在编译时就赋值了。
JVM初始化步骤
1、假如这个类还没有被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
初始化时机(只有当对类的主动使用的时候才会导致类的初始化)
– 创建类的实例,也就是new的方式
– 访问某个类或接口的静态变量,或者对该静态变量赋值
– 调用类的静态方法
– 反射(如Class.forName(“com.shengsiyuan.Test”))。会使类加载发生。
– 初始化某个类的子类,则其父类也会被初始化
– Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类
Java虚拟机结束生命周期
– 执行了System.exit()方法
– 程序正常执行结束
– 程序在执行过程中遇到了异常或错误而异常终止
– 由于操作系统出现错误而导致Java虚拟机进程终止
类加载有三种方式:
1、命令行启动应用时候由JVM初始化加载。
2、通过Class.forName()方法动态加载。
3、通过ClassLoader.loadClass()方法动态加载。
我觉得类初始化的发生条件也是类加载的条件。下篇来学习一下类加载器和双亲委派模型。