概述:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可已被虚拟机直接使用的Java类型,这就是类加载机制。
阶段:加载、验证、准备、解析、初始化、使用、卸载。
类加载的时机
加载可以交给虚拟机的具体实现来自有把握;
初始化的情况
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,使用new实例化对象的时候、读取或设置一个类的静态字段、调用一个类的静态方法的时候。
- 使用reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化;
- 当初始化一个类的时候,发现其父类还没有初始化,则需要先触发其父类初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会初始化这个主类。
接口和类初始化的区别
当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成初始化,只有在真正用到父接口时才会初始化。
类加载过程
加载
方法区(1.7之前叫永久代,从1.8开始叫元空间)
- 将类的字节码信息加载到元空间(方法区)中,将静态存储结构转换为方法区的运行时数据结构;
- 并且生成一个java.lang.Class对象,作为方法区这些数据的访问入口;
- 如果这个类还有父类没加载,则先加载父类;如果在加载过程中,父类被删除,则发生NoClassDefFoundError错误,即没有类定义被找到。
ClassNotFoundException 类没有被找到异常,发生在类加载阶段,加载类本身不存在;而NoClassDefFoundError是第一个类找到了,跟它关联的类没找到。
注意: 这里的字节码二进制流只并非是从class文件中获取,还有可能从其他地方获取。如网络中、数据库中、运行时计算生成等;
连接阶段
验证
- 验证类是否符合JVM规范、安全性检查;保证字节码的正确性,不会危害虚拟机自身安全;
- 文件格式验证:验证字节流是都符合class文件格式的规范;
- 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范要求;
- 字节码验证:保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
- 符号引用验证:对类自身以外的信息进行匹配性校验;
准备
为 类变量(被static修饰的变量) 分配内存并设置类变量初始值阶段,这些内存都将在方法区中进行分配;
解析
将常量池内的符号引用替换为直接引用的过程
初始化
在类初始化之前,会有一个特殊的构造方法,线程安全的,只会执行一次,用来收集所有静态代码块,静态变量的赋值语句操作,按顺序收集。然后以线程安全的方式来执行这个特殊构造;
初始化触发的时机:
- 首次访问这个类的静态变量或静态方法时;
- 子类初始化,如果父类还没有还没有,会触发;
- main方法所在的类,首先被初始化;
不会触发初始化
- 访问类的static final 变量不会触发初始化,甚至不会触发类的链接;
- 类对象.class不会触发初始化;
- 创建该类的数组不会触发初始化;
类加载器
概念:
“通过一个类的全限定名来获取描述此类的二进制字节流“ 把这个动作放到虚拟机外部去实现,以便额昂程序自己决定如何去获取所有的类,实现这个动作的代码被称为类加载器。
类的双亲委派加载机制
- 启动类加载器:负责加载jdk jre/lib中核心的类,例如:rt.jar,由C++实现,是虚拟机自身的一部分。
- 扩展类加载器:负责加载jre/lib/ext 扩展目录中的类;
- 应用程序类加载器(系统类加载器):加载classpath下的所有class类;
注意:从上到下 父亲关系
类的加载顺序:
- 先找到应用程序类加载器,调用loadClass();
- 找父亲类加载器即扩展类加载器,看是否类已经被加载;
- 如果没找到,再委托它的父亲(扩展)再去找启动类(扩展类加载器的父亲)加载器;
- 如果在再找到就返回,由自己去classPath中找;
如果没有这种委托机制,就有可能造成级别低的类加载器加载更通用的类,其他使用String的代码不能正常工作,像String这种比较通用的类应该由更高级别的类加载去加载;