瑞_JVM虚拟机_类的生命周期_初始化阶段 <clinit>

文章目录

    • 1 JVM虚拟机概述
    • 2 类的生命周期
      • 2.1 加载阶段
      • 2.2 连接阶段
      • 2.3 初始化阶段\<client> ★★★★★
        • 2.3.1 案例一
          • 2.3.1.1 案例描述
          • 2.3.1.2 解析字节码指令
        • 2.3.2 案例二
        • 2.3.3 小结
        • 2.3.4 代码中触发类的初始化的方式
          • 2.3.4.0 设置打印出加载并初始化的类
          • 2.3.4.1 方式一
          • 2.3.4.2 方式二
          • 2.3.4.3 方式三
          • 2.3.4.4 方式四
        • 2.3.5 大厂面试题
          • 2.3.5.1 题目
          • 2.3.5.2 分析
          • 2.3.5.3 结论
        • 2.3.6 \<clinit>指令的特殊情况
          • 2.3.6.1 情况一
          • 2.3.6.2 情况二
          • 2.3.6.3 情况三
        • 2.3.7 继承关系下的初始化情况
        • 2.3.8 数组的创建与初始化
        • 2.3.9 final修饰静态变量赋值非常量下的初始化(特殊情况)
      • 2.4 使用阶段
      • 2.5 卸载阶段
    • 附:JDK1.8运行时数据区
    • 附:数据类型的初始值表

🙊前言:本文章为瑞_系列专栏之《JVM虚拟机》的类的生命周期篇的初始化阶段小节。由于博主是从B站黑马程序员的《JVM虚拟机》学习其相关知识,所以本系列专栏主要是针对该课程进行笔记总结和拓展,文中的部分原理及图解等也是来源于黑马提供的资料,特此注明。本文仅供大家交流、学习及研究使用,禁止用于商业用途,违者必究!

瑞&3l

1 JVM虚拟机概述

瑞:请参考《瑞_JVM虚拟机_概述》




2 类的生命周期

  类的生命周期描述了一个类加载、使用、卸载的整个过程

  类的生命周期一般分为五个阶段:加载 ➡️ 连接 ➡️ 初始化 ➡️ 使用 ➡️ 卸载

在这里插入图片描述

瑞:初始化阶段最重要,因为程序员可以干涉

  由于连接阶段操作很多,所以,又可以分为七个阶段:加载 ➡️ 验证 ➡️ 准备 ➡️ 解析 ➡️ 初始化 ➡️ 使用 ➡️ 卸载

在这里插入图片描述




2.1 加载阶段

瑞:请参考《瑞_JVM虚拟机_类的生命周期》

2.2 连接阶段

瑞:请参考《瑞_JVM虚拟机_类的生命周期》

2.3 初始化阶段<client> ★★★★★

  加载 ➡️ 连接 ➡️ 初始化 ➡️ 使用 ➡️ 卸载

瑞:初始化阶段就是执行static代码块赋值。类的初始化包括执行类的<clinit>()方法,该方法由静态变量显式赋值代码和静态代码块组成,且只执行一次。如果有父类,先加载和初始化父类,再执行子类的<clinit>()方法。一个已经被初始化的类通常不会被再次初始化,这是因为Java虚拟机的设计旨在保证类加载和初始化的效率,避免重复的操作。

在这里插入图片描述
  连接阶段中非 final 修饰的静态变量(static)保存的是初始值(上图中 int 数据类型的初始值为 0,点我查看数据类型的初始值表),而且是保存在堆区里面的class对象中。但是最终这个值应该是1(如上图),当这个变量存储的值为1的时候,我们才能拿去使用。而这个赋值为1的操作就是在初始化阶段完成的。

  初始化阶段会执行静态代码块中的代码,并为静态变量赋值

  初始化阶段会执行字节码文件中 clinit 部分的字节码指令

瑞:clinit可以拆解为: cl —— class 代表类,init代表初始化。即类的初始化

2.3.1 案例一
2.3.1.1 案例描述
【案例】静态变量赋值

  在下面的案例代码中,声明了一个静态变量 value 赋值为 1 ,在静态代码块中将 value 值赋为 2 ,如下:

public class RayTest {

    public static int value = 1;

    static {
        value = 2;
    }

    public static void main(String[] args) {
        System.out.println("value = " + value);
    }
}

  执行后运行结果如下

	value = 2

  使用 jclasslib 工具查看字节码文件的方法信息,见下图

在这里插入图片描述

瑞:jclasslib 工具的安装可以参考《瑞_JVM虚拟机_概述》

  1️⃣ 其中 [0] <init>虽然我们没有写构造方法,但是编译器会帮我们自动生成默认无参构造方法

  2️⃣ 其中 [1] main 主方法

  3️⃣ 其中 [2] <clinit> 初始化阶段执行

  <clinit>方法中的字节码指令如下:

0 iconst_1
1 putstatic #7 <com/ray/onlytest/at2024/t03/t15/RayTest.value : I>
4 iconst_2
5 putstatic #7 <com/ray/onlytest/at2024/t03/t15/RayTest.value : I>
8 return

在这里插入图片描述

  《Java虚拟机规范》中putstatic指令是给类中的静态字段赋值,值从操作数栈中获取。iconst_常量值指令:将常量值放到操作数栈中(临时存放),生成常量。

  putstatic指令说明原文见下图⬇️

在这里插入图片描述

瑞:《Java虚拟机规范》官网地址:https://docs.oracle.com/javase/specs/index.html

2.3.1.2 解析字节码指令

  所以字节码指令执行步骤如下

0 iconst_1
1 putstatic #7 <com/ray/onlytest/at2024/t03/t15/RayTest.value : I>
4 iconst_2
5 putstatic #7 <com/ray/onlytest/at2024/t03/t15/RayTest.value : I>
8 return

  1️⃣ 编号0行:将常量1放入操作数栈中

在这里插入图片描述

  2️⃣ 编号1行:将操作数栈中的 1 赋值给常量池中编号为#7(你不一定是#7)的变量即RayTest.value,由于在连接阶段(准备阶段)中已经对静态变量RayTest.value分配内存并设置初始值 0 ,所以RayTest.value由 0 变为了 1

在这里插入图片描述

  3️⃣ 编号4行:将常量2放入操作数栈中

  4️⃣ 编号5行:将操作数栈中的 2 赋值给常量池中编号为#7(你不一定是#7)的变量即RayTest.value,所以RayTest.value由 1 变为了 2

2.3.2 案例二

  将案例一的两句静态代码对调顺序

public class RayTest {

    static {
        value = 2;
    }
    
    public static int value = 1;

    public static void main(String[] args) {
        System.out.println("value = " + value);
    }
}

  执行后运行结果如下

	value = 1

  <clinit>方法中的字节码指令如下:

0 iconst_2
1 putstatic #7 <com/ray/onlytest/at2024/t03/t15/RayTest.value : I>
4 iconst_1
5 putstatic #7 <com/ray/onlytest/at2024/t03/t15/RayTest.value : I>
8 return

在这里插入图片描述

  连接阶段 value 默认值设为了 0 ,然后在初始化阶段将 2 赋值给 value,再将 1 赋值给 value

2.3.3 小结

  clinit方法中字节码指令的执行顺序与Java中编写的顺序是一致的

2.3.4 代码中触发类的初始化的方式
2.3.4.0 设置打印出加载并初始化的类

添加-XX:+TraceClassLoading参数可以打印出加载并初始化的类

在这里插入图片描述

在这里插入图片描述

2.3.4.1 方式一

  1️⃣ 访问一个类的静态变量或者静态方法,注意如果这个变量是 final 修饰的并且等号右边是常量则不会触发类的初始化阶段(连接阶段就会直接给 final 修饰的常量赋值)。

public class RayTest {
    public static void main(String[] args) {
        int i  = RayTest2.i;
        System.out.println(i);
    }
}

class RayTest2{
    static {
        System.out.println("init...");
        i = 486;
    }
    public static int i = 0;
}

  运行后发现,RayTest2初始化了(打印了init…)

在这里插入图片描述

  将上面代码中RayTest2.i修改为 final 修饰

public class RayTest {
    public static void main(String[] args) {
        int i  = RayTest2.i;
        System.out.println(i);
    }
}

class RayTest2{
    static {
        System.out.println("init...");
//        i = 486;
    }
    public static final int i = 486;
}

  运行后发现,RayTest2并没有初始化(未打印init…)修改后RayTest2.i这个变量是 final 修饰的并且等号右边是常量486,则不会触发类的初始化阶段

在这里插入图片描述

2.3.4.2 方式二

  2️⃣ 调用Class.forName(String className)。

在这里插入图片描述

public class RayTest {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("com.ray.onlytest.at2024.t03.t16.RayTest2");
    }
}

class RayTest2{
    static {
        System.out.println("init...");
    }
}

  执行代码后,发现无论是否使用了RayTest2对象,只要调用了Class.forName(String className)方法,都会执行类的初始化过程

在这里插入图片描述

2.3.4.3 方式三

  3️⃣ new一个该类的对象时。

public class RayTest {
    static {
        System.out.println("RayTest初始化了...");
    }

    public static void main(String[] args) {
        new RayTest2();
    }
}

class RayTest2{
    static {
        System.out.println("RayTest2初始化了...");
    }
}

  执行代码后,发现先初始化main方法的当前类,即RayTest,然后初始化 new 的类RayTest2

在这里插入图片描述

2.3.4.4 方式四

  4️⃣ 执行Main方法的当前类。

  验证请见方法三,一样的代码和结论

2.3.5 大厂面试题
2.3.5.1 题目

  某大型互联网公司2019笔试题:以下代码的运行结果是DACBCB(换行省略)

public class Test1 {
    // DACBCB
    public static void main(String[] args) {
        System.out.println("A");
        new Test1();
        new Test1();
    }

    public Test1(){
        System.out.println("B");
    }

    {
        System.out.println("C");
    }

    static {
        System.out.println("D");
    }
}
2.3.5.2 分析

  clinit方法字节码指令如下:

0 getstatic #1 <java/lang/System.out : Ljava/io/PrintStream;>
3 ldc #9 <D>
5 invokevirtual #3 <java/io/PrintStream.println : (Ljava/lang/String;)V>
8 return

  3 ldc #9 <D>执行的是:从常量池中将字符串D加载到操作数栈
  5 invokevirtual #3 <java/io/PrintStream.println : (Ljava/lang/String;)V>执行的是:调用 println 方法打印操作数栈上的内容

  所以在执行main方法前,先初始化Test1的初始化方法,输出D,然后再执行main方法中的第一行System.out.println("A");输出A

  下一步执行main方法中的第一个new Test1();由于Test1类已经被初始化过了,所以Test1不会被再次加载初始化(不会执行static),new对象时会执行构造方法,构造方法的字节码指令init方法如下

在这里插入图片描述

 0 aload_0
 1 invokespecial #6 <java/lang/Object.<init> : ()V>
 4 getstatic #1 <java/lang/System.out : Ljava/io/PrintStream;>
 7 ldc #7 <C>
 9 invokevirtual #3 <java/io/PrintStream.println : (Ljava/lang/String;)V>
12 getstatic #1 <java/lang/System.out : Ljava/io/PrintStream;>
15 ldc #8 <B>
17 invokevirtual #3 <java/io/PrintStream.println : (Ljava/lang/String;)V>
20 return

  可以看到System.out.println("C");在编译之后,会在构造方法中执行❗️ 且顺序在System.out.println("B");执行之前。4、7、9执行的是打印C,而12、15、17执行的是打印B,所以当前输出:DACB

瑞:{},没有static的{}代码块是实例代码块(instance block),用于初始化对象的属性,在编译后会在构造方法内首先被执行

  同理,下一步执行main方法中的第二个new Test1();,会继续打印CB,所以最终结果是输出DACBCB

2.3.5.3 结论

  1️⃣ 因为main属于类的初始化阶段,首先执行static{},输出D。

  2️⃣ 然后main的第一行输出A

  3️⃣ new属于类的初始化,原本应该要执行static{},但是由于static{}只执行一次(Test1已经在步骤1️⃣被初始化了),所以执行{}和构造方法,{}没有static的{}代码块是实例代码块(instance block),用于初始化对象的属性。当创建类的对象时,实例代码块会被执行一次,输出C,然后执行构造方法中的语句,输出B,然后重复这个过程,输出CB。所以最终输出:DACBCB

通过这个案例我们得出结论:一个类被加载并初始化一般只会执行 1 次,而构造方法由于可以创建多个对象,每一次创建该对象都会执行一次构造方法。

2.3.6 <clinit>指令的特殊情况

  clinit指令在特定情况下不会出现,比如:如下几种情况是不会进行初始化指令执行

瑞:初始化阶段不一定存在。如果虚拟机认为初始化阶段可以什么都不用做(以下三种情况),则不会执行初始化指令

2.3.6.1 情况一

  1️⃣ 无静态代码块且无静态变量赋值语句

public class RayTest {

    public static void main(String[] args) {

    }

}

  使用 jclasslib 工具可以看到,直接就没有<clinit>方法,说明类的初始化阶段什么操作都没有进行

在这里插入图片描述

2.3.6.2 情况二

  2️⃣ 有静态变量的声明,但是没有赋值语句

public class RayTest {

    public static int i;

    public static void main(String[] args) {

    }

}

  使用 jclasslib 工具可以看到,情况2仍然没有<clinit>方法

在这里插入图片描述

2.3.6.3 情况三

  3️⃣ 静态变量的定义使用final关键字且右边是常量,这类变量会在准备阶段直接进行初始化

public class RayTest {

    public final static int i = 10;

    public static void main(String[] args) {

    }

}

  使用 jclasslib 工具可以看到,情况3仍然没有<clinit>方法

在这里插入图片描述

2.3.7 继承关系下的初始化情况

  1️⃣ 直接访问父类的静态变量,不会触发子类的初始化

  2️⃣ 子类的初始化clinit调用之前,会先调用父类的clinit初始化方法

  来看一个案例:某大型互联网公司2021笔试题,以下代码的输出结果是 2

public class RayTest {

    public static void main(String[] args) {
        new B02();
        System.out.println(B02.a);
    }

}

class A02 {
    static int a = 0;

    static {
        a = 1;
    }
}

class B02 extends A02 {
    static {
        a = 2;
    }
}

分析

  1️⃣ 调用new创建对象,需要初始化B02,但由于B02继承于A02,所以要优先初始化父类
  2️⃣ 初始化父类后,a = 1;
  3️⃣ 初始化之类后,a = 2;

  所以最后输出2

案例修改

  将new B02();注释后,输出结果为1,成功验证情况1️⃣ 访问父类的静态变量,只初始化父类

public class RayTest {

    public static void main(String[] args) {
//        new B02();
        System.out.println(B02.a);
    }

}

class A02 {
    static int a = 0;

    static {
        a = 1;
    }
}

class B02 extends A02 {
    static {
        a = 2;
    }
}
2.3.8 数组的创建与初始化

  数组的创建不会导致数组中元素的类进行初始化

  如以下代码运行结果为空,说明RayTest_A类没有执行初始化

public class RayTest {

    public static void main(String[] args) {
        RayTest_A[] arr = new RayTest_A[10];
    }
}

class RayTest_A {
    static {
        System.out.println("RayTest_A的静态代码块运行了...");
    }
}
2.3.9 final修饰静态变量赋值非常量下的初始化(特殊情况)

  final修饰的静态变量如果赋值的内容需要执行指令才能得出结果,会执行clinit方法进行初始化

瑞:如果一个静态变量使用了 final 关键词修饰,并且等号的右边是常量,则这个变量会在准备阶段直接进行初始化。但如果 final 修饰的静态变量的右边不是常量,是需要执行指令才能得出结果情况下(执行方法),由于赋值内容需要执行指令,所以该变量会在执行 clinit 初始化阶段方法中才进行初始化

public class RayTest {

    public static void main(String[] args) {
        System.out.println(RayTest_A.a);
    }
}

class RayTest_A {
    public static final int a = Integer.valueOf(1);
    static {
        System.out.println("RayTest_A的静态代码块运行了...");
    }
}

  运行代码后输出结果如下

	RayTest_A的静态代码块运行了...
	1



2.4 使用阶段

瑞:请参考《瑞_JVM虚拟机_类的生命周期》




2.5 卸载阶段

瑞:请参考《瑞_JVM虚拟机_类的生命周期》




附:JDK1.8运行时数据区

在这里插入图片描述

附:数据类型的初始值表


每一种基本数据类型和引用数据类型都有其初始值(见下表)

数据类型初始值
int0
long0L
short0
char‘\u0000’
byte0
booleanfalse
double0.0
引用数据类型null



本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/469922.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

three.js 鼠标左右拖动改变玩家视角

这里主要用到了 一个方法 obj.getWorldDirection(); obj.getWorldDirection()表示的获取obj对象自身z轴正方向在世界坐标空间中的方向。 按下 W键前进运动&#xff1b; <template><div><el-container><el-main><div class"box-card-left…

redis学习-List类型相关命令以及特殊情况分析

目录 1. lpush key value1 value2 ... 2. lrange key start end 3. lpop key num 4. rpush key value1 value2 ... 5. rpop key num 6. lindex key index 7. llen key 8. lrem key num value 9. rpoplpush key1 key2 10. lset key index value 11. linsert key before/after…

【STL】List容器介绍+相关练习题详细版本

List 1.List介绍2.使用注意3.list与vector的对比4.练习题 1.List介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;…

索引常见面试题

面试中&#xff0c;MySQL 索引相关的问题基本都是一系列问题&#xff0c;都是先从索引的基本原理&#xff0c;再到索引的使用场景&#xff0c;比如&#xff1a; 索引底层使用了什么数据结构和算法&#xff1f;为什么 MySQL InnoDB 选择 Btree 作为索引的数据结构&#xff1f;什…

5_springboot_shiro_jwt_多端认证鉴权_禁用Cookie

1. Cookie是什么 ​ Cookie是一种在客户端&#xff08;通常是用户的Web浏览器&#xff09;和服务器之间进行状态管理的技术。当用户访问Web服务器时&#xff0c;服务器可以向用户的浏览器发送一个名为Cookie的小数据块。浏览器会将这个Cookie存储在客户端&#xff0c;为这个Co…

30.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-数据搜索功能

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;29.数据推测功能…

从爬楼梯到斐波那契数列:解密数学之美

题目描述 我们来看看力扣的一道经典问题70. 爬楼梯 递归 假设n级台阶有climbStairs(n)种方法爬到楼梯顶。如果有n级台阶&#xff0c;如果第一次往上爬1级台阶&#xff0c;就会剩下n-1级台阶&#xff0c;这n-1级台阶就有climbStairs(n-1)种方法爬到楼梯顶&#xff1b;如果第一…

【Week Y2】使用自己的数据集训练YOLO-v5s

Y2-使用自己的数据集训练YOLO-v5s 零、遇到的问题汇总&#xff08;1&#xff09;遇到git的import error&#xff08;2&#xff09;Error&#xff1a;Dataset not found&#xff08;3&#xff09;Error&#xff1a;删除中文后&#xff0c;训练图片路径不存在 一、.xml文件里保存…

【MySQL】MySQL视图

文章目录 一、视图的基本使用1.创建视图2.修改了视图&#xff0c;对基表数据有影响3.修改了基表&#xff0c;对视图有影响4.删除视图 二、视图规则和限制 一、视图的基本使用 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一系列带有名称…

Vulnhub - Jarbas

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Jarbas 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/jarbas-1,232/ 0x01 信息收集 Nmap…

机器学习/深度学习绘图模板(PPT)

机器学习/深度学习绘图模板(PPT) 34页PPT&#xff0c;包含40图片模板 部分标注了论文出处 点击&#xff1a;我的B站工房 购买&#xff0c;粉丝专享价4.9元&#xff0c;线上交付&#xff0c;支付后自动发货。

Android Studio实现内容丰富的安卓民宿酒店预订平台

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看民宿 3.民宿预订 4.民宿预订支付&#xff0c; 5.支付订单 6.评论管…

MySQL的基本概念

一.MySQL概念&#xff1a; 你可以把MySQL想象成一个大杂货店&#xff0c;里面有很多货架&#xff0c;每个货架上摆放着不同种类的商品&#xff0c;MySQLMySQ就像是这个杂货店的后台库存管理系统。 1.表格&#xff08;货架&#xff09;&#xff1a;每个货架上摆放商品&#xff0…

Linux入门级别命令(下载远程连接工具)

$pwd&#xff08;当前所在位置&#xff0c;显示打印当前工作目录&#xff09;$mkdir 创建目录$cd dir 换个位置&#xff08;进入某一个目录&#xff09;$cd 什么都不加回到最开始的目录$ls当前目录位置下的文件信息&#xff08;列出当前所在位置下有哪些东西&#xff09;$mv移动…

Visual Studio 2013 - 调试模式下根据内存地址查看内存

Visual Studio 2013 - 调试模式下根据内存地址查看内存 1. 查看内存References 1. 查看内存 调试 -> 窗口 -> 内存 -> 内存1-4 References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

全栈的自我修养 ———— 让uniapp开发更加舒服!!(与别的博主思路不一样,小编这里只讲实用的,直提重点!)

小编是web的&#xff0c;然后现在开始接手微信小程序&#xff0c;有很多不习惯的的地方&#xff0c;经过一段时间的使用&#xff0c;部分得到了妥善的解决方法 一、用vscode开发小程序二、组件库的选择三、注意 一、用vscode开发小程序 发现用Hbuilder开发小程序有很多不习惯的…

企业专业化管理金字塔:技能进阶与案例分析

在纷繁复杂的企业管理领域中&#xff0c;一套行之有效的管理技能体系对于企业的稳健发展至关重要。本文将深入探讨企业专业化管理金字塔的五个层次&#xff1a;基本的管理技能、业务操作管理技能、组织管理技能、组织开发技能以及管理转变技能&#xff0c;并结合实际案例&#…

默写单词cpp(初学者版本)

笔摔坏了直接使用版:yum:仔细学习版:yum:1.直接使用版:yum:&#xff08;文件使用规范&#xff09;(1)文件(2)使用规范 2.仔细学习版。将会讲各个函数的功能和细节。今天太晚了&#xff0c;明天再写。 笔摔坏了 在一个阳光明媚的早晨&#xff0c;我愉快的奋笔疾书&#xff0c;抄…

Gradle v8.5 笔记 - 从入门到进阶(基于 Kotlin DSL)

目录 一、前置说明 二、Gradle 启动&#xff01; 2.1、安装 2.2、初始化项目 2.3、gradle 项目目录介绍 2.4、Gradle 项目下载慢&#xff1f;&#xff08;万能解决办法&#xff09; 2.5、Gradle 常用命令 2.6、项目构建流程 2.7、设置文件&#xff08;settings.gradle.…

docker入门(三)—— 安装docker

docker 安装 环境要求 本次使用的是云服务器&#xff0c;版本是 centos&#xff0c;要求版本在3.10以上 [rootiZbp15293q8kgzhur7n6kvZ /]# uname -r 3.10.0-1160.108.1.el7.x86_64 [rootiZbp15293q8kgzhur7n6kvZ /]# cat /etc/os-release NAME"CentOS Linux" VE…