做公司网站怎么做,专业网站建设费用,网站的信息管理建设的必要性,展示型企业网站有哪些举例1.概述
在 Java 中#xff0c;类加载过程是指将 Java 类的字节码加载到内存中#xff0c;并转换为 Java 虚拟机能够识别和执行的数据结构的过程。类加载是 Java 虚拟机执行 Java 程序的必要步骤之一#xff0c;它负责加载程序中用到的类和接口。下图所示是 ClassLoader 加载…1.概述
在 Java 中类加载过程是指将 Java 类的字节码加载到内存中并转换为 Java 虚拟机能够识别和执行的数据结构的过程。类加载是 Java 虚拟机执行 Java 程序的必要步骤之一它负责加载程序中用到的类和接口。下图所示是 ClassLoader 加载一个 .class 文件到 JVM 时需要经过的步骤 类从被加载到虚拟机内存中开始到卸载出内存为止它的整个生命周期可以简单概括为 7 个阶段加载Loading、验证Verification、准备Preparation、解析Resolution、初始化Initialization、使用Using和卸载Unloading。其中验证、准备和解析这三个阶段可以统称为连接Linking。
其中使用Using和卸载Unloading不属于类加载过程这里不需要过多关注接下来我们就来看看JVM底层是怎么加载class文件的。
2.类加载过程
根据上面的概述可知JVM加载 Class 类型的文件主要三步加载-连接-初始化。连接过程又可分为三步验证-准备-解析。过程如下图所示 2.1 加载(Loading)
加载是“类加载”过程的第一阶段为了不让大家混淆这两个名字接下来我都用**装载(Loading)**来叙述类加载过程的第一阶段。所谓装载简而言之就是将Java类的字节码文件加载到机器内存中并在内存中构建出Java类的原型即类模板Class对象。
在装载阶段虚拟机需要完成以下三件事情
通过一个类的全限定名来获取其定义的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在Java堆中生成一个代表这个类的 java.lang.Class 对象作为对方法区中这些数据的访问入口。
装载这一阶段主要是通过类加载器完成的类加载器是另一个重要的知识点后面我会安排总结。每个 Java 类都有一个引用指向加载它的 ClassLoader。不过数组类不是通过 ClassLoader 创建的而是 JVM 在需要的时候自动创建的数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。这就意味着我们可以自定义一个类加载器来加载类实现隔离可控。 项目推荐基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装解决业务开发时常见的非功能性需求防止重复造轮子方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化做到可插拔。严格控制包依赖和统一版本管理做到最少化依赖。注重代码规范和注释非常适合个人学习和企业使用 Github地址https://github.com/plasticene/plasticene-boot-starter-parent Gitee地址https://gitee.com/plasticene3/plasticene-boot-starter-parent 微信公众号Shepherd进阶笔记 交流探讨qunShepherd_126 2.2 链接(Linking)
链接阶段包括三个子阶段验证、准备和解析。
2.2.1 验证Verification
验证阶段主要确保类的字节码符合 Java 虚拟机规范以及不会危害 Java 虚拟机的安全。主要包括文件格式验证、元数据验证、字节码验证和符号引用验证等。
文件格式验证验证字节流是否符合 Class 文件格式的规范。例如是否以 0xCAFEBABE 开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。元数据验证对字节码描述的信息进行语义分析注意对比 javac 编译阶段的语义分析以保证其描述的信息符合 Java 语言规范的要求。例如这个类是否有父类除了 java.lang.Object 之外。字节码验证通过数据流和控制流分析确定程序语义是合法的、符合逻辑的。符号引用验证确保解析动作能正确执行。
验证阶段是非常重要的但不是必须的它对程序运行期没有影响如果所引用的类经过反复验证那么可以考虑采用 -Xverifynone 参数来关闭大部分的类验证措施以缩短虚拟机类加载的时间。
2.2.2 准备Preparation
准备阶段为类的静态变量分配内存并设置默认初始值。这些变量在编译期间已经确定了初始值但在此阶段还未赋值。静态变量也称为类变量即被 static 关键字修饰的变量
这里所设置的初始值通常情况下是数据类型默认的零值如 0、0L、null、false 等而不是被在 Java 代码中被显式地赋予的值。比如我们定义了public static int n666 那么 n 变量在准备阶段的初始值就是 0 而不是 666初始化阶段才会赋值因为在这个阶段并不会像初始化阶段中那样会有初始化或者我们自己编写的Java代码被执行。各个数据类型的零值如下所示 我们前面特别强调是”通常情况“下这就说明有特殊情况需要注意比如给 n 变量加上了 final 关键字public static final int n6 那么准备阶段 n 的值就被赋值为 6。还有以下几点需要注意 对基本数据类型来说对于类变量(static)和全局变量如果不显式地对其赋值而直接使用则系统会为其赋予默认的零值而对于局部变量来说在使用前必须显式地为其赋值否则编译时不通过。示例如下 可以看到局部变量i没有赋值就使用代码爆红编译通不过而类变量n和全局变量m都没有赋值但是可以在直接使用上面代码去掉局部变量i编译通过之后控制会打印两行1因为对n,m进行了操作。 对于同时被 static 和 final 修饰的常量必须在声明的时候就为其显式地赋值否则编译时不通过而只被 final 修饰的常量则既可以在声明时显式地为其赋值也可以在类初始化时显式地为其赋值总之在使用前必须为其显式地赋值系统不会为其赋予默认零值。 直接编译不通过。 对于引用数据类型 reference 来说如数组引用、对象引用等如果没有对其进行显式地赋值而直接使用系统都会为其赋予默认的空值即 null 。如果在数组初始化时没有对数组中的各元素赋值那么其中的元素将根据对应的数据类型而被赋予默认的“空”值。 public class Test {private static Integer num;private int[] arr;void method() {System.out.println(num);System.out.println(arr);}public static void main(String[] args) {Test test new Test();test.method();}
}运行代码控制输出为 null
null2.2.3 解析Resolution
解析阶段将类、接口、字段和方法的符号引用解析为直接引用。符号引用是一组符号来描述所引用的目标而直接引用是直接指向目标的指针、句柄或偏移量。
2.3 初始化Initialization
初始化阶段是类加载过程的最后一个阶段它负责执行类的静态变量赋值和静态代码块的初始化操作。在此阶段Java 虚拟机按照程序中定义的顺序执行类的静态变量赋值和静态代码块中的代码。即到了初始化阶段才真正开始执行类中定义的 Java 程序代码。
初始化阶段的重要工作是执行类的初始化方法: ()方法。该方法仅能由Java编译器生成并由JVM调用程序开发者无法自定义一个同名的方法更无法直接在Java程序中调用该方法虽然该方法也是由字节码指令所组成。它是由类静态成员的赋值语句以及static语句块合并产生的。
() : 只有在给类的中的static的变量显式赋值或在静态代码块中赋值了。才会生成此方法。 () 一定会出现在Class的method表中。
在 Java 中对类变量进行初始值设定有两种方式
1、声明类变量是指定初始值。
2、使用静态代码块为类变量指定初始值。
public class InitializationTest {public static int id 1;public static int number;static {number 2;System.out.println(father static);}
}clinit()该方法仅能由Java编译器生成并由JVM调用那么我们怎么验证上面案例执行初始化了呢这时候就需要在IDEA中安装一个插件: jclasslib Bytecode viewer 如果没有外网不能直接在IDEA插件市场下载安装请自行去网上下载查找安装包本地安装即可。安装好之后对我们要查看的代码进行编译 代码编译完之后按如下点击如果安装插件没有这个选项请重启IDEA再看看… 就能看到如下信息了可以看到有初始化执行的方法clinit 在加载一个类之前虚拟机总是会试图加载该类的父类因此父类的总是在子类之前被调用。也就是说父类的static块优先级高于子类。 接着上面代码写一个子类继承上面的父类
public class SubInitialization extends InitializationTest {static{number 6;//number属性必须提前已经加载一定会先加载父类。System.out.println(son static{});}public static void main(String[] args) {System.out.println(number);}
}控制打印如下
father static
son static{}
6那是不是所有类编译都会生成clinit方法呢显然不是的。因为初始化这个阶段只是负责执行类的静态变量赋值和静态代码块的初始化操作的。也就是以下情况不会生成此方法
一个类中并没有声明任何的类变量也没有静态代码块时一个类中声明类变量但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时一个类中包含static final修饰的基本数据类型的字段这些类字段初始化语句采用编译时常量表达式
public class Test {//场景1对于非静态的字段不管是否进行了显式赋值都不会生成clinit()方法public int n 1;//场景2静态的字段没有显式的赋值不会生成clinit()方法public static int n1;//场景3比如对于声明为static final的基本数据类型的字段不管是否进行了显式赋值都不会生成clinit()方法public static final int n2 1;
}你可以按上面步骤编译之后利用插件看看有没有生成clinit进行验证。
接下来谈谈使用static final修饰的字段的显式赋值的操作到底是在哪个阶段进行的赋值
public class Test {public static int n 1; //在初始化阶段赋值public static final int INT_CONSTANT 10; //在链接阶段的准备环节赋值public static Integer INTEGER_CONSTANT1 Integer.valueOf(100); //在初始化阶段赋值public static final Integer INTEGER_CONSTANT2 Integer.valueOf(1000); //在初始化阶段赋值public static final String s0 hello; //在链接阶段的准备环节赋值public static final String s1 new String(hello); //在初始化阶段赋值public static String s2 hello; //在初始化阶段赋值public static final int NUM1 new Random().nextInt(10); //在初始化阶段赋值static int a 9;//在初始化阶段赋值static final int b a; //在初始化阶段赋值}有两种情况
1、在链接阶段的准备环节赋值 对于基本数据类型的字段来说如果使用static final修饰则显式赋值(直接赋值常量而非调用方法通常是在链接阶段的准备环 节进行 对于String来说如果使用字面量的方式赋值使用static final修饰的话则显式赋值通常是在链接阶段的准备环节进行
2、情况2在初始化阶段()中赋值。排除上述的在准备环节赋值的情况之外的情况。
总结什么时候在链接阶段的准备环节给此全局常量附的值是字面量或常量。不涉及到方法或构造器的调用。除此之外都是在初始化环节赋值的。
什么时候对类进行初始化
当创建一个类的实例时比如使用new关键字或者通过反射、克隆、反序列化。当调用类的静态方法时即当使用了字节码invokestatic指令。当使用类、接口的静态字段时(final修饰特殊考虑)比如使用getstatic或者putstatic指令。当使用java.lang.reflect包中的方法反射类的方法时。比如Class.forName(“com.atguigu.java.Test”)当初始化子类时如果发现其父类还没有进行过初始化则需要先触发其父类的初始化。如果一个接口定义了default方法那么直接实现或者间接实现该接口的类的初始化该接口要在其之前被初始化。当虚拟机启动时用户需要指定一个要执行的主类包含main()方法的那个类虚拟机会先初始化这个主类。当初次调用 MethodHandle 实例时初始化该 MethodHandle 指向的方法所在的类。涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类
下面情况是不会进行类初始化的 当访问一个静态字段时只有真正声明这个字段的类才会被初始化。当通过子类引用父类的静态变量不会导致子类初始化 通过数组定义类引用不会触发此类的初始化 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。 调用ClassLoader类的loadClass()方法加载一个类并不是对类的主动使用不会导致类的初始化。
3.总结
需要注意的是类加载过程是由 Java 虚拟机和类加载器共同完成的。Java 虚拟机提供了规范定义了类加载过程的各个阶段而类加载器负责执行具体的加载任务。Java 虚拟机默认提供了多个类加载器它们按照一定的委托关系组成了类加载器层次结构每个类加载器负责加载不同位置的类