网站百度商桥,郑州妇科,天津建设工程信息网渤海油田,制作团体网站简介
在Java的世界里#xff0c;每一个类或者接口#xff0c;在经历编译器后#xff0c;都会生成一个个.class文件。
类加载机制指的是将这些.class文件中的二进制数据读入到内存中#xff0c;并对数据进行校验#xff0c;解析和初始化。最终#xff0c;每一个类都会在…简介
在Java的世界里每一个类或者接口在经历编译器后都会生成一个个.class文件。
类加载机制指的是将这些.class文件中的二进制数据读入到内存中并对数据进行校验解析和初始化。最终每一个类都会在方法区保存一份它的元数据在堆中创建一个与之对应的Class对象。
类的生命周期经历7个阶段分别是加载、验证、准备、解析、初始化、使用、卸载。
除了使用和卸载两个过程前面的5个阶段 加载、验证、准备、解析、初始化 的执行过程就是类的加载过程。 类加载的时机
大多数人在问 “类什么时候加载” 和 “类什么时候初始化” 从语境上来说都是在问同一个问题就是这个.class文件什么时候被读取到虚拟机的内存中并且达到可用的状态。
但严格意义上来说加载和初始化是类生命周期的两个阶段。
对于什么时候加载Java虚拟机规范中并没有约束各个虚拟机都可以按自身需要来自由实现。但绝大多数情况下都遵循“什么时候初始化”来进行加载。
什么时候初始化Java虚拟机规范有明确规定当符合以下条件时包括但不限于虚拟机内存中没有找到对应类型信息则必须对类进行“初始化”操作
使用new实例化对象时、读取或者设置一个类的静态字段或方法时反射调用时例如 Class.forName(com.xxx.MyTest)初始化一个类的子类会首先初始化子类的父类Java虚拟机启动时标明的启动类JDK8 之后接口中存在default方法这个接口的实现类初始化时接口会其之前进行初始化
初始化阶段开始之前自然还是要先经历 加载、验证、准备 、解析的。 类的加载过程
从简介中我们知道类的加载过程分 5 个阶段其中 验证、准备、解析 可以归纳为 “连接” 阶段。
需要注意的是这5个阶段并不是严格意义上的按顺序完成在类加载的过程中这些阶段会互相混合交叉运行最终完成类的加载和初始化。
例如在加载阶段需要使用验证的能力去校验字节码正确性。在解析阶段也要使用验证的能力去校验符号引用的正确性。或者加载阶段生成Class对象的时候需要解析阶段符号引用转直接引用的能力等等...... 接下来我们详细分解一下这5个阶段都做了什么事情。
加载
加载是类加载过程的第一个阶段在加载阶段虚拟机需要完成以下三件事情
通过一个类的全限定名去找到其对应的.class文件将这个.class文件内的二进制数据读取出来转化成方法区的运行时数据结构在Java堆中生成一个代表这个类的java.lang.Class对象作为对方法区中这些数据的访问入口
Java虚拟机并没有规定类的字节流必从.class文件中加载在加载阶段程序员可以通过自定义的类加载器自行定义读取的地方例如通过网络、数据库等。 验证
Class文件中的内容是字节码这些内容可以由任何途径产出验证阶段的目的是保证文件内容里的字节流符合Java虚拟机规范且这些内容信息运行后不会危害虚拟机自身的安全。
验证阶段会完成以下校验
文件格式验证验证字节流是否符合Class文件格式的规范。例如是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型 ...... 等等
元数据验证对字节码描述的元数据信息进行语义分析要符合Java语言规范。例如是否继承了不允许被继承的类例如final修饰过的、类中的字段、方法是否和父类产生矛盾 ...... 等等
字节码验证对类的方法体进行校验分析确保这些方法在运行时是合法的、符合逻辑的。
符号引用验证发生在解析阶段符号引用转为直接引用的时候例如确保符号引用的全限定名能找到对应的类、符号引用中的类、字段、方法允许被当前类所访问 ...... 等等
验证阶段是非常重要的但不是必须的它对程序运行期没有影响如果所引用的类经过反复验证那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施以缩短虚拟机类加载的时间。
验证阶段不是必须的虽然这个阶段非常重要。Java虚拟机允许程序员主动取消这个阶段用来缩短类加载的时间可以根据自身需求使用 -Xverify:none参数来关闭大部分的类验证措施。 验证
Class文件中的内容是字节码这些内容可以由任何途径产出验证阶段的目的是保证文件内容里的字节流符合Java虚拟机规范且这些内容信息运行后不会危害虚拟机自身的安全。
验证阶段会完成以下校验
文件格式验证验证字节流是否符合Class文件格式的规范。例如是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型 ...... 等等
元数据验证对字节码描述的元数据信息进行语义分析要符合Java语言规范。例如是否继承了不允许被继承的类例如final修饰过的、类中的字段、方法是否和父类产生矛盾 ...... 等等
字节码验证对类的方法体进行校验分析确保这些方法在运行时是合法的、符合逻辑的。
符号引用验证发生在解析阶段符号引用转为直接引用的时候例如确保符号引用的全限定名能找到对应的类、符号引用中的类、字段、方法允许被当前类所访问 ...... 等等
验证阶段是非常重要的但不是必须的它对程序运行期没有影响如果所引用的类经过反复验证那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施以缩短虚拟机类加载的时间。
验证阶段不是必须的虽然这个阶段非常重要。Java虚拟机允许程序员主动取消这个阶段用来缩短类加载的时间可以根据自身需求使用 -Xverify:none参数来关闭大部分的类验证措施。
准备
这个阶段类的静态字段信息即使用 static 修饰过的变量会得到内存分配并且设置为初始值。
对于该阶段有以下几个知识点需要注意
1、内存分配仅包括 static 修饰过的变量而不包括实例变量实例变量得等到对象实例化时分配内存。
2、初始值指的是变量数据类型的默认值而不是被在Java代码中被显式地赋予的值。但是当字段信息被 final 修饰成常量ConstantValue时这个初始值就是Java代码中显式地赋予的值。
例如public static int value 3 类变量 value 在准备阶段设置的初始值 是 0不是 3。把value赋值为3的 putstatic 指令是在程序编译后存放于类构造器 clinit() 方法中的所以把 value 赋值为 3 的动作将在初始化阶段才会执行。 当使用 final 修饰后public static final int value 3 类变量 value 在准备阶段设置的初始值 是 3不是 0。 3、在JDK8取消永久代后方法区变成了一个逻辑上的区域这些类变量的内存实际上是分配在Java堆中的。
解析
这个阶段虚拟机会把这个Class文件中常量池内的符号引用转换为直接引用。主要解析的是 类或接口、字段、类方法、接口方法、方法类型、方法句柄等符号引用。我们可以把解析阶段中符号引用转换为直接引用的过程理解为当前加载的这个类和它所引用的类正式进行“连接“的过程。
什么是符号引用 Java代码在编译期间是不知道最终引用的类型具体指向内存中哪个位置的这时候会用一个符号引用来表示具体引用的目标是谁。Java虚拟机规范中明确定义了符号引用的形式符合这个规范的前提下符号引用可以是任意值只要能通过这个值能定位到目标。 什么是直接引用 直接引用就是可以直接或间接指向目标内存位置的指针或句柄。 引用的类型还未加载初始化怎么办 当出现这种情况会触发这个引用对应类型的加载和初始化。 初始化
这是类加载的最后一个步骤啦初始化的过程就是执行类构造器 clinit()方法的过程。
当初始化完成之后类中static修饰的变量会赋予程序员实际定义的“值”同时类中如果存在static代码块也会执行这个静态代码块里面的代码。
clinit() 方法的作用是什么
还记得么在准备阶段已经对类中static修饰的变量赋予了初始值。clinit() 方法的作用就是给这些变量赋予程序员实际定义的“值”。同时类中如果存在static代码块也会执行这个静态代码块里面的代码。 clinit() 方法是什么
clinit() 方法 和 init 方法是不同的它们一个是“类构造器”一个是实例构造器。 Java虚拟机会保证子类clinit() 方法在执行前父类的 clinit() 已经执行完毕。而 init 方法则需要显性的调用父类的构造器。 clinit() 方法由编译器自动生成但不是必须生成的只有这个类存在static修饰的变量或者类中存在静态代码块但时候才会自动生成clinit()方法 加载过程总结
当一个符合Java虚拟机规范的字节流文件经历 加载、验证、准备、解析、初始化这些阶段相互协作执行完成之后加载阶段读取到的Class字节流信息会按虚拟机规定的格式在方法区保存一份然后Java 堆中会创建一个 java.lang.Class 类的对象这个对象描述了这个类所有信息也提供了这个类在方法区的访问入口。
方法区中使用同一加载器的情况下每个类只会有一份Class字节流信息 Java堆中使用同一加载器的情况下每个类只会有一份 java.lang.Class 类的对象 类加载器
还记得在加载阶段通过类的全限定名获取该类字节流数据的这个动作么类加载器就是用来实现这个动作的。
当年为了满足浏览器上 Java Applet 的需求Java的开发团队设计了类加载器它独立于Java虚拟机外部允许程序员按自身需要自行实现类加载器。这是一项非常优秀的创新它让同一个类可以实现访问隔离、OSGi、程序热部署等等。发展至今类加载器已经是Java技术体系的一块重要基石。
三层类加载器介绍
启动类加载器Bootstrap Class Loader负责加载JAVA_HOME\lib 目录或者被 -Xbootclasspath 参数制定的路径例如 jre/lib/rt.jar 里所有的class文件。由C实现不是ClassLoader子类。
拓展类加载器Extension Class Loader负责加载Java平台中扩展功能的一些jar包包括JAVA_HOME\lib\ext 目录中 或 java.ext.dirs 指定目录下的jar包。由Java代码实现。
应用程序类加载器Application Class Loader我们自己开发的应用程序就是由它进行加载的负责加载ClassPath路径下所有jar包。
双亲委派模型
高端的食材往往只需要最简单的烹饪方式而保证Java程序稳定运行的双亲委派模式其实也非常简单
双亲委派模式其实一句话就可以说清楚任何一个类加载器在接到一个类的加载请求时都会先让其父类进行加载只有父类无法加载或者没有父类的情况下才尝试自己加载。 ClassLoader 类中有示例如下
protected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException
{// 首先要保证线程安全synchronized (getClassLoadingLock(name)) {// 先判断这个类是否被加载过Class? c findLoadedClass(name);if (c null) {try {// 有父类优先交给父类尝试加载if (parent ! null) {c parent.loadClass(name, false);} else {c findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 父类加载失败这里捕获异常但不需要做任何处理}if (c null) {// 没有父类或者父类无法加载尝试自己加载c findClass(name);}}if (resolve) {resolveClass(c);}return c;}
}双亲委派模型好处是什么
在解答这个问题前需要先了解一个知识点不同的类加载器加载同一个类结果是虚拟机里会存在两份这个类的信息所以当判断这两个类是否“相等”时必定是不相等的。
使用双亲委派模式可以保证每一个类只会有一个类加载器。例如Java最基础的Object类它存放在 rt.jar 之中这是 Bootstrap 的职责范围当向上委派到 Bootstrap 时就会被加载。
但如果没有使用双亲委派模式可以任由自定义加载器进行加载的话Java这些核心类的API就会被随意篡改。