JVM虚拟机调优指导(四)——类加载机制与编译优化_symbol: class jvmproxyconfigurer-程序员宅基地

技术标签: jvm  java  学习笔记系列  

1 类文件结构

1.1 MyDemo

public class MyDemo {
    
    private int a = 1;
    private int b = 2;
    public int sum() {
    
        int c =  a + b;
        return c;
    }
    public static void main(String[] args) {
    
        MyDemo myDemo = new MyDemo();
        int sum = myDemo.sum();
        System.out.println(sum);
    }
}

上面的java文件编译得到的class文件,class文件是一个16进制字节码的二进制文件,我们通过winhex软件打开如下:
在这里插入图片描述

1.2 字节码

1.2.1 字节码表

需要说明的是,class文件只有两种数据类型: 无符号数 和 表。

  • 无符号数
    无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值。
    其中无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节。

  • 表是由多个无符号数或其他表构成的复合数据结构。
    所有的表都以“_info”结尾,由于表没有固定长度,所以通常会在其前面加上个数说明。
类型 名称 说明 长度
u4 magic 魔数,识别Class文件格式 4个字节
u2 minor_version 副版本号 2个字节
u2 major_version 主版本号 2个字节
u2 constant_pool_count 常量池计算器 2个字节
cp_info constant_pool 常量池 n个字节
u2 access_flags 访问标志 2个字节
u2 this_class 类索引 2个字节
u2 super_class 父类索引 2个字节
u2 interfaces_count 接口计数器 2个字节
u2 interfaces 接口索引集合 2个字节
u2 fields_count 字段个数 2个字节
field_info fields 字段集合 n个字节
u2 methods_count 方法计数器 2个字节
method_info methods 方法集合 n个字节
u2 attributes_count 附加属性计数器 2个字节
attribute_info attributes 附加属性集合 n个字节
1.2.2 魔数

在class文件中,前4个字节就是魔数,如下:
在这里插入图片描述
魔数是用来区分文件类型的一种标识,0XCAFEBABE (咖啡豆/咖啡宝贝)表示就是class文件。

1.2.3 版本号

魔数后面的4位就是版本号了,同样也是4个字节,其中前2个字节表示副版本号,后2个字节表示主版本号。
在这里插入图片描述
前面两个字节是0x0000,也就是其值为0; 后面两个字节是0x0034,也就是其值为52; 所以上面的代码就是52.0版本
来编译的,也就是jdk1.8.0。

1.3 常量池

在版本号的后面就是常量池了。

1.3.1 常量池容量计算

由于常量池的数量不固定,所以需要通过2个字节来记录常量池的大小。
在这里插入图片描述其值为0X0027,用十进制表示就是39,需要注意的是,常量池中只有38个常量。

1.3.2 常量类型和结构

常量池中的每一项都是一个表,其项目类型共有14种,如下表格所示:

类型 标志 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MothodType_info 16 标志方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点
1.3.3 第一个常量

在这里插入图片描述
可以看到,第一个常量的值为10,对应到标志位,找到常量为 CONSTANT_Methodref_info (类中方法的符号引用),它的结构为:
在这里插入图片描述
后面的4个字节都是它的内容,记录着2个索引值:
在这里插入图片描述
第一个索引值为:0X0009,也就是9,指向常量池中第9项的索引。
第二个索引值为:0X0017,也就是23,指向常量池中第23项的索引。

1.3.4 第二个常量

在这里插入图片描述
可以看到,第二个常量的值为9,对应到表中的标志位,找到常量为 CONSTANT_Fieldref_info(字段的符号引用),它的结构为:
在这里插入图片描述
后面的4个字节都是它的内容,记录着2个索引值:
在这里插入图片描述
第一个索引值为:0X0004,也就是4,指向常量池中第4项的索引。
第二个索引值为:0X0018,也就是24,指向常量池中第24项的索引。

1.3.5 javap编译字节码

通过javap命令就可以将class文件转化为可读的字节码指令。

javap -v MyDemo.class > MyDemo.txt

生成的字节码指令如下:

Classfile /E:/xxx/jvm/MyDemo.class
  Last modified 2020-7-20; size 564 bytes
  MD5 checksum 0c32d2c6a7edb1bebe91f0a2f168d28d
  Compiled from "MyDemo.java"
public class cn.learn.jvm.MyDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#23         // java/lang/Object."<init>":()V
   #2 = Fieldref           #4.#24         // cn/learn/jvm/MyDemo.a:I
   #3 = Fieldref           #4.#25         // cn/learn/jvm/MyDemo.b:I
   #4 = Class              #26            // cn/learn/jvm/MyDemo
   #5 = Methodref          #4.#23         // cn/learn/jvm/MyDemo."<init>":()V
   #6 = Methodref          #4.#27         // cn/learn/jvm/MyDemo.sum:()I
   #7 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V
   #9 = Class              #32            // java/lang/Object
  #10 = Utf8               a
  #11 = Utf8               I
  #12 = Utf8               b
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               sum
  #18 = Utf8               ()I
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               SourceFile
  #22 = Utf8               MyDemo.java
  #23 = NameAndType        #13:#14        // "<init>":()V
  #24 = NameAndType        #10:#11        // a:I
  #25 = NameAndType        #12:#11        // b:I
  #26 = Utf8               cn/itcast/jvm/MyDemo
  #27 = NameAndType        #17:#18        // sum:()I
  #28 = Class              #33            // java/lang/System
  #29 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
  #30 = Class              #36            // java/io/PrintStream
  #31 = NameAndType        #37:#38        // println:(I)V
  #32 = Utf8               java/lang/Object
  #33 = Utf8               java/lang/System
  #34 = Utf8               out
  #35 = Utf8               Ljava/io/PrintStream;
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               (I)V
{
    
  public cn.learn.jvm.MyDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field a:I
         9: aload_0
        10: iconst_2
        11: putfield      #3                  // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 5: 4
        line 6: 9

  public int sum();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: aload_0
         5: getfield      #3                  // Field b:I
         8: iadd
         9: istore_1
        10: iload_1
        11: ireturn
      LineNumberTable:
        line 9: 0
        line 10: 10

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #4                  // class cn/itcast/jvm/MyDemo
         3: dup
         4: invokespecial #5                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #6                  // Method sum:()I
        12: istore_2
        13: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #8                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 14: 0
        line 15: 8
        line 16: 13
        line 17: 20
}
SourceFile: "MyDemo.java"

内容大致分为4个部分:
第一部分:显示了生成这个class的java源文件、版本信息、生成时间等。
第二部分:显示了该类中所涉及到常量池,共38个常量。
第三部分:显示该类的构造器,编译器自动插入的。
第四部分:显示了sum、main方的信息。(这个是需要我们重点关注的)

1.4 描述符

1.4.1 字段描述符

官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2
在这里插入图片描述

1.4.2 方法描述符

官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3
示例:
The method descriptor for the method:

Object m(int i, double d, Thread t) {…}

is:

(IDLjava/lang/Thread;)Ljava/lang/Object;

1.5 实例:字符串拼接

字符串的拼接在开发过程中使用是非常频繁的,常用的方式有三种:

  • +号拼接: str+“456”
  • StringBuilder拼接
  • StringBuffer拼接

StringBuffer是保证线程安全的,效率是比较低的,我们更多的是使用场景是不会涉及到线程安全的问题的,所以更多的时候会选择StringBuilder,效率会高一些。那么,问题来了,StringBuilder和“+”号拼接,哪个效率高呢?
通过探究字节码可知:使用+号拼接,但是在字节码中也被编译成了StringBuilder方式。
所以,可以得出结论,字符串拼接,+号和StringBuilder是相等的,效率一样。

2 类加载机制

Java源代码经过编译器编译成字节码之后,最终都需要加载到虚拟机之后才能运行。虚拟机把描述类的数据从Class 文件加载到内存,并对数据进行校验转换解析初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。

2.1 类加载时机

一个类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。
加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。
注意:是按部就班的开始,这些阶段通常都是互相交叉地混合进行的,会在一个阶段执行的过程中调用、激活另一个阶段。
对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):

  • 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令;
  • 使用 java.lang.reflect 包的方法对类进行反射调用的时候;
  • 当初始化一个类的时候,发现其父类还没有进行初始化的时候,需要先触发其父类的初始化
  • 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个类;
  • 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果
    REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有初始化。
  • 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

除此之外,所有引用类型的方式都不会触发初始化,称为被动引用。
比如:

  • 通过子类引用父类的静态字段,不会导致子类的初始化;
  • 通过数组定义来引用类,不会触发此类的初始化;
  • 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化;

2.2 类加载过程

2.2.1 加载

在加载阶段,Java虚拟机需要完成以下三件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2.2.2 验证

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

  • 文件格式验证
    第一阶段要验证字节流是否符合 Class 文件格式的规范。验证点主要包括:
    1)是否以魔数 0xCAFEBABE 开头;
    2)主、次版本号是否在当前虚拟机处理范围之内;
    3)常量池的常量中是否有不被支持的常量类型;
    4)Class 文件中各个部分及文件本身是否有被删除的或者附加的其它信息等等。
  • 元数据验证
    第二阶段是对字节码描述的信息进行语义分析
    1)这个类是否有父类;
    2)这个类的父类是否继承了不允许被继承的类;
    3)如果这个类不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法;
    4)类中的字段、方法是否与父类产生矛盾等等。
  • 字节码验证
    第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证
    1)最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段–解析阶段中发生。
    2)符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验,通俗来说就是,该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。
2.2.3 准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。

2.2.4 解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

2.2.5 初始化

类初始化阶段是类加载过程中的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全是由虚拟机主导和控制的。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。

2.3 类加载器

实现"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作的代码被称为“类加载器”(Class Loader)。

2.3.1 类与类加载器

比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

2.3.2 双亲委派模型

从 Java 虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++ 来实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由 Java 来实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader 。

从 Java 开发者的角度来看,类加载器可以划分为:

  • 启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在 <java_home>\lib 目录中的类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,那直接使用 null 代替即可;
  • 扩展类加载器(Extension ClassLoader):这个类加载器由sun.misc.Launcher$ExtClassLoader 实现,它负责加载 <java_home>\lib\ext 目录中,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器;
  • 应用程序类加载器(Application ClassLoader):这个类加载器由 sun.misc.Launcher$App-
    ClassLoader 实现。getSystemClassLoader() 方法返回的就是这个类加载器,因此也被称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
双亲委派模型的工作过程是:
1)如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类
2)而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此
3)因此所有的加载请求最终都应该传送到最顶层的启动类加载器
4)只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
这样做的好处就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object,它放在 rt.jar 中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器来加载,因此Object 类在程序的各种类加载器环境中都是同一个类。
双亲委派模型确保在程序的各种类加载器环境中没有重复的类,对于保证Java程序的稳定运作极为重要!

3 编译优化

3.1 前端编译器

前端编译器就是将*.java文件编译成*.class文件的过程。

3.1.1 javac编译过程

javac的编译过程大致可以分为1个准备过程和3个处理过程,它们分别如下所示。

  • 准备过程:初始化插入式注解处理器。
  • 解析与填充符号表过程
    1)词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。
    2)填充符号表。产生符号地址和符号信息。符号表(Symbol Table)是由一组符号地址和符号信息构成的数据结构。
  • 插入式注解处理器的注解处理过程。
  • 分析与字节码生成过程
    1)标注检查。对语法的静态信息进行检查。
    2)数据流及控制流分析。对程序动态运行过程进行检查。
    3)解语法糖。将简化代码编写的语法糖还原为原有的形式。
    4)字节码生成。将前面各个步骤所生成的信息转化成字节码。

3.2 后端编译优化

后端编译器是指把Class文件转换成与本地基础设施(硬件指令集、操作系统)相关的二进制机器码的过程。

  • 即时编译器
    在HotSpot中有三种编译器
    客户端编译器”(Client Compiler)
    服务端编译器”(Server Compiler)
    Graal编译器(JDK10中出现)
  • 提前编译器
    Android中的ART就是提前编译器
3.2.1 编译器的优化
3.2.2 方法内联

是指JVM在运行时将调用次数达到一定阈值的方法调用替换为方法体本身。
例如:

static class B {
    
    int value;
    final int get() {
    
        return value;
    }
}
    //未内联的代码
    public void foo() {
    
        y = b.get();
        // ...do stuff...
        z = b.get();
        sum = y + z;
    }
    //内联优化的代码
    public void foo() {
    
        y = b.value;
        // ...do stuff...
        z = b.value;
        sum = y + z;
    }
    //可以继续优化
    //冗余存储消除的代码
    public void foo() {
    
        y = b.value;
        // ...do stuff...
        z = y;
        sum = y + z;
    }
    //复写传播的代码
    public void foo() {
    
        y = b.value;
        // ...do stuff...
        y = y;
        sum = y + y;
    }
    //进行无用代码消除的代码
    public void foo() {
    
        y = b.value;
        // ...do stuff...
        sum = y + y;
    }

需要注意的是,以上优化是在编译器内部完成,而不是在代码中完成的,只是用代码来说明问题。

3.2.3 逃逸分析

逃逸分析的基本原理是:

  • 分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;
  • 甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;
  • 从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。

如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低,则可能为这个对象实例采取不同程度的优化。

3.2.4 公共子表达式消除

它的含义是:如果一个表达式E之前已经被计算过了,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就称为公共子表达式。
对于这种表达式,没有必要花时间再对它重新进行计算,只需要直接用前面计算过的表达式结果代替E。

如果这种优化仅限于程序基本块内,便可称为局部公共子表达式消除,如果这种优化的范围涵盖了多个基本块,那就称为全局公共子表达式消除。
例子:

//源码
int d = (c * b) * 12 + a + (a + b * c);
//编译器检测到cb与bc是一样的表达式,而且在计算期间b与c的值是不变
int d = E * 12 + a + (a + E);
//还可以进一步优化
int d = E * 13 + a + a;

3.2.5 数组边界检查消除

如果有一个数组foo[],在Java语言中访问数组元素foo[i]的时候系统将会自动进行上下界的范围检查,即i必须满足“i>=0&&i<foo.length”的访问条件,否则将抛出一个运行时异常:
java.lang.ArrayIndexOutOfBoundsException。
对于编译器而言,根据数据流分析来确定是否越界,如果没有越界,那么执行时就不需要再判断了。
如果在循环中,本身就是通过循环变量来控制对数组的访问,执行时也就不用再判断了,
这样就可以把整个数组的上下界检查消除掉,这可以节省很多次的条件判断操作。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jianchang91/article/details/107411273

智能推荐

Docker 快速上手学习入门教程_docker菜鸟教程-程序员宅基地

文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程

电脑技巧:Windows系统原版纯净软件必备的两个网站_msdn我告诉你-程序员宅基地

文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你

vue2封装对话框el-dialog组件_<el-dialog 封装成组件 vue2-程序员宅基地

文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_

MFC 文本框换行_c++ mfc同一框内输入二行怎么换行-程序员宅基地

文章浏览阅读4.7k次,点赞5次,收藏6次。MFC 文本框换行 标签: it mfc 文本框1.将Multiline属性设置为True2.换行是使用"\r\n" (宽字符串为L"\r\n")3.如果需要编辑并且按Enter键换行,还要将 Want Return 设置为 True4.如果需要垂直滚动条的话将Vertical Scroll属性设置为True,需要水平滚动条的话将Horizontal Scroll属性设_c++ mfc同一框内输入二行怎么换行

redis-desktop-manager无法连接redis-server的解决方法_redis-server doesn't support auth command or ismis-程序员宅基地

文章浏览阅读832次。检查Linux是否是否开启所需端口,默认为6379,若未打开,将其开启:以root用户执行iptables -I INPUT -p tcp --dport 6379 -j ACCEPT如果还是未能解决,修改redis.conf,修改主机地址:bind 192.168.85.**;然后使用该配置文件,重新启动Redis服务./redis-server redis.conf..._redis-server doesn't support auth command or ismisconfigured. try

实验四 数据选择器及其应用-程序员宅基地

文章浏览阅读4.9k次。济大数电实验报告_数据选择器及其应用

随便推点

灰色预测模型matlab_MATLAB实战|基于灰色预测河南省社会消费品零售总额预测-程序员宅基地

文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件

log4qt-程序员宅基地

文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt

100种思维模型之全局观思维模型-67_计算机中对于全局观的-程序员宅基地

文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的

线程间控制之CountDownLatch和CyclicBarrier使用介绍_countdownluach于cyclicbarrier的用法-程序员宅基地

文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法

自动化监控系统Prometheus&Grafana_-自动化监控系统prometheus&grafana实战-程序员宅基地

文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战

React 组件封装之 Search 搜索_react search-程序员宅基地

文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search