00-JAVA基础-JVM类加载机制及自定义类加载器

news2024/10/7 10:24:42

JVM 类加载机制

JVM类加载机制是Java运行时环境的核心部分,它负责将类的.class文件加载到JVM中,并将其转换为可以被JVM执行的数据结构。

类加载的整体流程

类加载的整体流程可以分为五个阶段:加载(Loading)、链接(Linking)、初始化(Initialization)、使用和卸载(Unloading)。其中,链接阶段又可以细分为验证(Verification)、准备(Preparation)和解析(Resolution)三个阶段。

加载阶段

  • 系统提供的类加载器或自定义类加载器:首先,通过类加载器(可以是系统提供的,也可以是用户自定义的)根据类的全名(包括包名)找到对应的.class文件。
  • 读取.class文件:类加载器从文件系统或网络等位置读取.class文件的二进制数据。
  • 创建Class对象:将读取到的二进制数据转换为方法区的运行时数据结构,并在堆区创建一个java.lang.Class对象,这个Class对象作为该类在JVM中的元数据表示。

链接阶段

  • 验证阶段:
    • 文件格式验证:验证.class文件是否符合JVM规范,是否是一个有效的字节码文件。
    • 元数据验证:验证字节码中的元数据是否符合Java语言规范。
    • 字节码验证:验证字节码的执行是否符合Java虚拟机规范。
    • 符号引用验证:验证类中的符号引用是否有效,能否被正确解析。
    • 准备阶段:为类的静态变量分配内存空间,并设置初始值(注意,这里的初始值不是代码中显式赋予的值,而是根据变量的数据类型赋予的默认值,如int为0,引用类型为null)。
    • 解析阶段:将常量池中的符号引用转换为直接引用。符号引用是一个抽象的概念,如字段名、方法名等,而直接引用则是指向内存中的具体地址。

初始化阶段

  • 初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有变量的赋值动作和静态代码块(static代码块)中的语句合并产生的。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先初始初始化其父类。
  • JVM会保证一个类的()方法在多线程环境中被正确的加锁和同步。因此JVM中一个类的class是线程安全的。
  • 只有当类或接口的静态变量被首次主动使用时,JVM才会初始化这个类或接口。
类的主动引用(一定会发生类的初始化
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当JVM启动,java Hello,则一定会初始化Hello类(即先启动main方法说在的类)
  • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类

注意:
一个类被初始化后,不会重复进行被初始化

类的被动引用(不会发生类的初始化
  • 当访问一个静态属性是,只有真正声明这个域的类才会被初始化
    • 通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(因为常量在编译节点就存入调用类的常量池中了

使用和卸载阶段

  • 类被初始化后,就可以被JVM使用,创建实例对象、调用方法等。
  • 当类不再被使用时,JVM会将其从内存中卸载,释放其占用的资源。

测试类的加载机制

创建一个A对象和一个B对象,使B对象继承A,测试时初始化A,查看JVM类的加载过程

A.java

package demo;

public class A {

    /** 定义一个静态属性 */
    public static String NAME = "A";

    /** 定义一个final 静态常量 */
    public static final int AGE = 20;

    static {
        System.out.println("A static 代码块被调用了");
        NAME = "static 修改了 NAME";
    }

    public A(){
        System.out.println("A 默认构造方法被调用了");
    }

}

B.java

package demo;

public class B extends A {

    /** 定义一个静态属性 */
    private static String TYPE = "A";

    /** 定义一个final 静态常量 */
    private static final int WIDTH = 20;

    static {
        System.out.println("B static 代码块被调用了");
        TYPE = "static 修改了 TYPE";
    }

    public B(){
        System.out.println("B 默认构造方法被调用了");
    }

}

ClassLoadDemo.java

package demo;

/**
 * 测试JVM类的加载机制
 *
 * @author Anna.
 * @date 2024/4/5 14:14
 */
public class ClassLoadDemo {

  static {
    System.out.println("mian方法所在类的 static 代码块被调用了");
  }

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

    System.out.println("初始化完成后-第二次调用不会重复进行初始化");
    new B();
  }
}

执行结果:

在这里插入图片描述

结论:

一个类初始化时,如果其父类没有被初始化,则先初始化其父类。

一个类被初始化后,再次被引用时,则不会重复初始化。

new 对象会默认调用类的无参构造方法

测试类的主动引用,一定会发生类的初始化

类定义使用上述A.java及B.java

  • 通过new一个对象
public class ClassLoadDemo1 {

  public static void main(String[] args) {
    System.out.println("========1 通过new========");
    new A();
  }
}

执行结果:
在这里插入图片描述

  • 调用类的静态成员(除了final常量)和静态方法
package demo;

public class ClassLoadDemo1 {

  public static void main(String[] args) {
    System.out.println("========2 调用类的静态成员(除了final常量)和静态方法========");
    System.out.println("调用final常量:");
    System.out.println("A.AGE:" + A.AGE);
    System.out.println("调用非final静态成员常量:");
    System.out.println("A.NAME:" + A.NAME);
  }
}

执行结果:

在这里插入图片描述

  • 使用java.lang.reflect包的方法对类进行反射调用
package demo;
public class ClassLoadDemo1 {

    public static void main(String[] args) throws Exception {
        System.out.println("========3 反射调用========");
        Class.forName("demo.A");
    }
}

执行结果:

在这里插入图片描述

类的被动引用,不会发生类的初始化

  • 通过子类引用父类的静态变量,不会导致子类初始化
package demo;
public class ClassLoadDemo2 {

    public static void main(String[] args) {
        System.out.println("========通过子类引用父类的静态变量,不会导致子类初始化========");
        System.out.println("========子类引用父类final常量,既不初始化父类,也不初始化子类========");
        System.out.println("B.AGE" + B.AGE);
        System.out.println("========子类引用父类非final常量,初始化父类,但不初始化子类========");
        System.out.println("B.NAME" + B.NAME);
    }
}

执行结果:

在这里插入图片描述

  • 通过数组定义引用,不会触发此类的初始化
package demo;
public class ClassLoadDemo2 {

    public static void main(String[] args) {
        System.out.println("========2 通过数组定义引用,不会触发此类的初始化========");
        A[] arr = new A[10];

    }
}

执行结果:

在这里插入图片描述

类加载器

  • Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的重要组成部分,负责动态加载Java类到Java虚拟机的内存空间中。
  • 类加载器在Java程序中扮演着至关重要的角色,它确保了类的正确加载、链接和初始化,为程序的执行提供了基础。

类加载器的层次结构

Java类加载器的层次结构是一个有序的组织形式,它定义了类加载器之间的父子关系和加载范围,确保了Java程序的正确运行。

Java类加载器的层次结构通常包括四种主要类型的类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)和自定义类加载器。这些类加载器按照父子关系组织,形成了一个有序的加载链。

  • 启动类加载器(Bootstrap ClassLoader):
    • 是Java虚拟机的一部分,通常由本地代码实现(C语言),负责加载Java核心类库,如java.lang包中的类。
    • 由于是由本地代码实现的,因此并不基础自java.lang.classLoader。在Java代码中无法直接获取其引用。
    • 负责加载JVM运行时环境所需的基础类库,是所有类加载器的根加载器。
  • 扩展类加载器(Extension ClassLoader):
    • 是由Java语言实现的,用于加载Java扩展类库,如javax包中的类。
    • 它的父加载器是启动类加载器(但无法通过java获取其父类)。
    • 可以通过系统属性"java.ext.dirs"来指定扩展类库的路径。
  • 应用程序类加载器(Application ClassLoader):
    • 也称为系统类加载器,是默认的类加载器,负责加载应用程序的类路径(classpath,java.lang.path)下的类文件。
    • 它的父加载器是扩展类加载器。
    • 可以通过ClassLoader类的getSystemClassLoader()方法获取到它的引用。
    • 有sum.misc.Launcher$AppClassLoader实现
  • 自定义类加载器
    • 开发者可以通过基础java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊需求。

层次结构的优势

  • 安全性:通过双亲委派机制,确保了Java核心类库的安全性,防止了恶意代码通过自定义类加载器来篡改或替换核心类库。
  • 有序性:层次结构确保了类的加载是有序的,避免了类的重复加载。
  • 灵活性:通过自定义类加载器,可以实现热加载、隔离加载环境、加载加密类文件等高级功能。

ClassLoader介绍

作用
  • java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对于的字节代码,然后从这些字节代码中定义出第一个Java类,即java.lang.Class类的一个实例。
相关方法
方法描述
loadClass(String name)这是 ClassLoader 的核心方法,用于加载指定的类。当应用程序请求加载一个类时,ClassLoader 首先会检查这个类是否已经被加载过。如果已经加载过,就直接返回该类的 Class 对象。否则,会尝试加载该类。
如果 ClassLoader 自己无法找到类,它会根据双亲委派模型将请求委托给父类加载器。如果父类加载器也无法加载类,那么 ClassLoader 会尝试自己加载类。
findClass(String name)这是一个受保护的方法,用于查找指定名称的类。当 ClassLoader 需要自己加载类时,它会调用该方法。具体的类加载逻辑通常会在该方法中实现。
在自定义 ClassLoader 时,开发人员可以重写该方法以实现自己的类加载逻辑。
findLoadedClass(String name)该方法用于查找已经由当前 ClassLoader 加载的类。如果该类已经被加载,则返回对应的 Class 对象;否则返回 null。
findSystemClass(String name)该方法用于查找由系统 ClassLoader(通常是 AppClassLoader)加载的类。它不会委托给父类加载器,而是直接在当前 ClassLoader 或系统 ClassLoader 中查找类。
defineClass(String name, byte[] b, int off, int len)该方法用于将字节码数组转换为 Class 对象。它允许从字节码数组直接定义类,而不需要从文件系统或网络加载类文件。
在某些高级场景中,如动态代理或代码生成,开发人员可能会使用该方法动态地创建类。
getResource(String name) 和 getResources(String name)这两个方法用于查找资源(如文件、图像等)。它们根据类加载器的类路径查找资源,并返回 URL 对象或 URL 对象的枚举。
getParent()该方法返回当前 ClassLoader 的父类加载器。通过该方法,可以访问双亲委派模型中的父级加载器。

案例

package demo2;
public class ClassLoadDemo {
    public static void main(String[] args) {
        System.out.printf("获取当前应用程序类加载器:%s%n", ClassLoader.getSystemClassLoader());
        System.out.printf("获取应用程序类加载器父类加载器:%s%n", ClassLoader.getSystemClassLoader().getParent());
        System.out.printf("获取根加载器:%s%n", ClassLoader.getSystemClassLoader().getParent().getParent());

        System.out.printf("获取应用类路径:%s%n", System.getProperty("java.class.path"));
    }
}

执行结果:

在这里插入图片描述

类加载器的代理模式

  • 代理模式
    • 交给其他加载器来加载指定的类
  • 双亲委派机制
    • 在代理模式下,当一个类加载器收到类加载请求时,它首先会检查这个类是否已经被加载过。如果已经加载过,就直接返回这个类的Class对象。如果没有加载过,它会将这个请求委派给父类加载器去完成。这种机制称为双亲委派模型。通过逐级向上委派,最终会到达顶层的启动类加载器。如果启动类加载器无法加载该类,那么请求会逐级向下传递,直到找到能够加载该类的类加载器为止。
    • 双亲委派机制是为了保证java核心库的类型安全。这种机制保证了不会出现用户自己定义java.lang.Object类的情况
    • 类加载器除了用于加载类,也是安全的最基本的屏障
  • 双亲委派机制是代理模式的一种
    • 并不是所有的类加载器都采用双亲委派机制
    • tomcat服务器加载器也使用代理模式,所不同的是它是首先尝试去加载这个类,如果找不到在代理给父类加载器。

自定义类加载器

自定义类加载器流程:

  • 继承:java.lang.ClassLoader
  • 首先检查请求的类型是否已经被这个加载装载到了命名空间,如果已加载,则直接返回
  • 委派类加载器请求给父类加载器,如果父类加载器能够完成加载,则直接返回加载器加载的Class实例
  • 调用本类加载器的findClass()方法,试图获取对应的字节码,如果获取的到,则调用defineClass()导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(),
  • 测试案例调用:loadClass()加载class

UserDo.java

package demo3;

/**
 * UserDo 实体
 * @author Anna.
 * @date 2024/4/5 16:31
 */
public class UserDo1 {
    private String name;
    public UserDo1(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "UserDo{" +
                "name='" + name + '\'' +
                '}';
    }
}

注意: 该类编译完成后.class文件不要放在应用目录下。JVM双亲委派机制会先使用应用类加载器进行加载,这样会导致无法测试自定义类加载器

CustomClassLoader.java

package demo3;

import java.io.*;

/**
 * 自定义类加载器
 *
 * 首先检查请求的类型是否已经被这个加载装载到了命名空间,如果已加载,则直接返回
 * + 委派类加载器请求给父类加载器,如果父类加载器能够完成加载,则直接返回加载器加载的Class实例
 * + 调用本类加载器的findClass()方法,试图获取对应的字节码,如果获取的到,则调用defineClass()导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(),loadClass()转抛异常,终止加载过程
 * @author Anna.
 * @date 2024/4/5 16:07
 */
public class CustomClassLoader extends ClassLoader{

    /** 定义一个加载根路径 */
    private String rootDir;

    public CustomClassLoader(String rootDir){
        this.rootDir = rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 加载该类是否已经加载到命名空间
        Class<?> clazz = findLoadedClass(name);
        if (clazz != null) {
            return clazz;
        }

        // 尝试自己加载类
        byte[] classData;
        try {
            classData = getClassData(name);
        } catch (IOException e) {
            throw new ClassNotFoundException();
        }

        // 如果自己也加载不到该类,则抛出异常
        if(classData == null){
            throw new ClassNotFoundException();
        }

        // 调用defineClass()导入类型到方法区
        clazz = defineClass(name, classData, 0, classData.length);

        return clazz;
    }

    /**
     * 根据路径读取.class文件
     *
     * @param name
     * @return byte[]
     * @author Anna.
     * @date 2024/4/5 16:24
     */
    private byte[] getClassData(String name) throws IOException {
        // 将包路径转换为类路径
        String path = rootDir + File.separator + name.replace(".", File.separator) + ".class";
        // 使用字节流读取class文件
        InputStream is  = null;
        ByteArrayOutputStream baos  = null;
        try{
            is = new FileInputStream(path);
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int temp = -1;
            while ((temp = is.read(buffer)) != -1){
                baos.write(buffer,0,temp);
            }
        }
        catch (Exception e){
            throw e;
        }
        finally {
            if(is != null){
                is.close();
            }
            if(baos != null){
                baos.close();
            }
        }

        return baos.toByteArray();
    }
}

ClassLoaderDemo.java

package demo3;

/**
 * 自定义类加载器 测试
 *
 * @author Anna.
 * @date 2024/4/5 16:33
 */
public class ClassLoaderDemo {

    public static void main(String[] args) throws ClassNotFoundException {

        // 获取根路径
        String path = "D:";
        System.out.println(path);

        // 获取自定义类加载器
        CustomClassLoader customClassLoader = new CustomClassLoader(path);

        // 使用同一个类加载器加载UserDo
        Class<?> clazz1 = customClassLoader.loadClass("demo3.UserDo");
        Class<?> clazz2 = customClassLoader.loadClass("demo3.UserDo");
        System.out.printf("clazz1 hashCode:%s%n", clazz1.hashCode());
        System.out.printf("clazz2 hashCode:%s%n", clazz2.hashCode());
        System.out.printf("判断同一个类加载器加载同一个对象,class是否相同:%s%n", clazz1 == clazz2);
        System.out.printf("获取clazz1的类加载器:%s%n", clazz1.getClassLoader());

        // 重新创建一个自定义类加载器
        CustomClassLoader customClassLoader2 = new CustomClassLoader(path);

        // 使用不同类加载器加载UserDo
        Class<?> clazz3 = customClassLoader2.loadClass("demo3.UserDo");
        System.out.printf("clazz3 hashCode:%s%n", clazz3.hashCode());
        System.out.printf("判断不同类加载器加载同一个对象,class是否相同:%s%n", clazz1 == clazz3);
        System.out.printf("获取clazz3的类加载器:%s%n", clazz1.getClassLoader());
    }

}

执行结果:

在这里插入图片描述

注意:被两个类加载器加载同一个类,JVM不认为是相同的类。

加密解密类加载器

可以通过取反操作或者DES对称秘钥进行加密解密

EncrptUtils.java

package demo4;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 取反加密工具
 *
 * @author Anna.
 * @date 2024/4/5 17:36
 */
public class EncrptUtils {

    /**
     * 文件字节取反加密
     *
     * @param src  原路径
     * @param dest 目标路径
     * @return void
     * @author Anna.
     * @date 2024/4/5 17:39
     */
    public static void inversion(File src, File dest) throws IOException {
        try (FileInputStream is = new FileInputStream(src); FileOutputStream baos = new FileOutputStream(dest)) {
            int temp = -1;
            while ((temp = is.read()) != -1) {
                // 取反
                baos.write(temp ^ 0xff);
            }
        } catch (Exception e) {
            throw e;
        }
    }
}

DecrptClassLoader.java

package demo4;

import java.io.*;

/**
 * 自定义解密类加载器
 *
 * @author Anna.
 * @date 2024/4/5 16:07
 */
public class DecrptClassLoader extends ClassLoader {

    /**
     * 定义一个加载根路径
     */
    private String rootDir;

    public DecrptClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 加载该类是否已经加载到命名空间
        Class<?> clazz = findLoadedClass(name);
        if (clazz != null) {
            return clazz;
        }

        // 尝试自己加载类
        byte[] classData;
        try {
            classData = getClassData(name);
        } catch (IOException e) {
            throw new ClassNotFoundException();
        }

        // 如果自己也加载不到该类,则抛出异常
        if (classData == null) {
            throw new ClassNotFoundException();
        }

        // 调用defineClass()导入类型到方法区
        clazz = defineClass(name, classData, 0, classData.length);

        return clazz;
    }

    /**
     * 根据路径读取.class文件
     *
     * @param name
     * @return byte[]
     * @author Anna.
     * @date 2024/4/5 16:24
     */
    private byte[] getClassData(String name) throws IOException {
        // 将包路径转换为类路径
        String path = rootDir + File.separator + name.replace(".", File.separator) + ".class";
        // try-with-resources
        try (InputStream is = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            // 使用字节流读取class文件 字节取反
            int temp = -1;
            while ((temp = is.read()) != -1) {
                // 字节取反
                baos.write(temp ^ 0xff);
            }
            return baos.toByteArray();
        }
    }
}

ClassLoadDemo.java

package demo4;

import java.io.File;
import java.io.IOException;

/**
 * 自定义解密类加载器
 * * 1 加密UserDo.class文件
 * * 2 使用上一案例中类加载器加载加密后的class
 * * 3 使用解密类加载器加载
 *
 * @author Anna.
 * @date 2024/4/5 17:36
 */
public class ClassLoadDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 1 加密.class文件
        EncrptUtils.inversion(new File("D://demo3/UserDo.class"), new File("D://temp/demo3/UserDo.class"));

        // 获取根路径
        String path = "D:/temp";

        // 2 使用上一案例中类加载器加载加密后的class
        // 获取自定义类加载器    报错ClassNotFoundException
//        CustomClassLoader customClassLoader = new CustomClassLoader(path);
//        Class<?> clazz1 = customClassLoader.loadClass("demo3.UserDo");

        // 3 使用解密类加载器加载
        DecrptClassLoader decrptClassLoader = new DecrptClassLoader(path);
        Class<?> clazz2 = decrptClassLoader.loadClass("demo3.UserDo");
        System.out.printf("获取clazz3的类加载器:%s%n", clazz2.getClassLoader());
    }
}

执行结果:

在这里插入图片描述

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java数据结构与算法刷题-----LeetCode238. 除自身以外数组的乘积

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 动态规划&#xff1a;左右乘积列表2. 滚动数组对动态规划过程…

flutter组件_AlertDialog

官方说明&#xff1a;A Material Design alert dialog. 翻译&#xff1a;一个材料设计警告对话框。 作者释义&#xff1a;显示弹窗&#xff0c;类似于element ui中的Dialog组件。 AlertDialog的定义 const AlertDialog({super.key,this.icon,this.iconPadding,this.iconColor,t…

Cortex-M4架构

第一章 嵌入式系统概论 1.1 嵌入式系统概念 用于控制、监视或者辅助操作机器和设备的装置&#xff0c;是一种专用计算机系统。 更宽泛的定义&#xff1a;是在产品内部&#xff0c;具有特定功能的计算机系统。 1.2 嵌入式系统组成 硬件 ①处理器&#xff1a;CPU ②存储器…

分布式事务 - 个人笔记 @by_TWJ

目录 1. 传统事务1.1. 事务特征1.2. 事务隔离级别1.2.1. 表格展示1.2.2. oracle和mysql可支持的事务隔离级别 2. 分布式事务2.1. CAP指标2.2. BASE理论2.3. 7种常见的分布式事务方案2.3.1. 2PC2.3.2. 3PC2.3.3. TCC2.3.3.1. TCC的注意事项&#xff1a;2.3.3.2. TCC方案的优缺点…

【Java面试题】JVM(26道)

文章目录 JVM面试题基础1.什么是JVM&#xff1f;2.JVM的组织架构&#xff1f; 内存管理3.JVM的内存区域是什么&#xff1f;3.1堆3.2方法区3.3程序计数器3.4Java虚拟机栈3.5本地方法栈 4.堆和栈的区别是什么&#xff1f;5.JDK1.6、1.7、1.8内存区域的变化&#xff1f;6.内存泄露…

mynet开源库

1.介绍 个人实现的c开源网络库&#xff0e; 2.软件架构 1.结构图 2.基于event的自动分发机制 3.多优先级分发队列&#xff0c;延迟分发队列 内部event服务于通知机制的优先级为0&#xff0c;外部event优先级为1&#xff0e; 当集中处理分发的event_callback时&#xff0c…

鸿蒙ArkUI声明式学习:【UI资源管理】

OpenHarmony 应用的资源分类和资源的访问以及应用开发使用的像素单位以及各单位之间相互转换的方法。 资源分类 移动端应用开发常用到的资源比如图片&#xff0c;音视频&#xff0c;字符串等都有固定的存放目录&#xff0c;OpenHarmony 把这些应用的资源文件统一放在 resourc…

线程的666种状态

文章目录 在Java中&#xff0c;线程有以下六种状态&#xff1a; NEW&#xff1a;新建状态&#xff0c;表示线程对象已经被创建但还未启动。RUNNABLE&#xff1a;可运行状态&#xff0c;表示线程处于就绪状态&#xff0c;等待系统分配CPU资源执行。BLOCKED&#xff1a;阻塞状态…

Centos Docker Oracle11g 密码过期修改

症状&#xff1a; Centos Oracle11g环境变量配置 如果没有配置环境变量&#xff0c;需要先配置Oracle环境变量&#xff0c;否则执行sqlplus时会提示&#xff1a;SP2-0750: You may need to set ORACLE_HOME to your Oracle software directory 配置方法&#xff1a; 第一步&a…

《系统架构设计师教程(第2版)》第8章-系统质量属性与架构评估-03-ATAM方法架构评估实践(下)

文章目录 3. 测试阶段3.1 头脑风暴和优先场景&#xff08;第7步&#xff09;3.1.1 理论部分3.1.2 示例 3.2 分析架构方法&#xff08;第8步&#xff09;3.2.1 调查架构方法1&#xff09;安全性2&#xff09;性能 3.2.2 创建分析问题3.2.3 分析问题的答案胡佛架构银行体系结构 3…

初学ELK - elk部署

一、简介 ELK是3个开源软件组合&#xff0c;分别是 Elasticsearch &#xff0c;Logstash&#xff0c;Kibana Elasticsearch &#xff1a;是个开源分布式搜索引擎&#xff0c;提供搜集、分析、存储数据三大功能。它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自…

开源免费的多功能PDF工具箱

它支持修改PDF、编辑PDF书签、导出PDF书签、导入书签、生成、合并、拆分、提取页面内容、提取图片、OCR 功能介绍: 修改PDF信息&#xff1a;修改文档属性、页码编号、页面链接、页面尺寸&#xff1b;删除自动打开网页等动作&#xff0c;去除复制及打印限制&#xff1b;设置阅读…

ios swift5 “Sign in with Apple“(使用苹果登录)怎样接入(第三方登录)集成AppleID登录

文章目录 截图1.在开发者网站的app id中添加Sign in with Apple功能2.在Xcode中添加Sign in with Apple功能3.代码&#xff1a;只有第一次登录的时候可以获取到用户名参考博客chatGPT答案 截图 1.在开发者网站的app id中添加Sign in with Apple功能 1.1 如果你新建app id,记得在…

OpenHarmony实战:物联网解决方案之芯海cst85芯片移植案例

本文介绍基于芯海cst85芯片的cst85_wblink开发板移植OpenHarmony LiteOS-M轻量系统的移植案例。 开发了Wi-Fi连接样例和XTS测试样例&#xff0c;同时实现了wifi_lite, lwip, startup, utils, xts, hdf等部件基于OpenHarmony LiteOS-M内核的适配。 移植架构上采用Board和Soc分…

流式密集视频字幕

流式密集视频字幕 摘要1 IntroductionRelated Work3 Streaming Dense Video Captioning Streaming Dense Video Captioning 摘要 对于一个密集视频字幕生成模型&#xff0c;预测在视频中时间上定位的字幕&#xff0c;理想情况下应该能够处理长的输入视频&#xff0c;预测丰富、…

GEE:样本点的样式设置

作者:CSDN @ _养乐多_ 本文将介绍在Google Earth Engine (GEE)平台上为样本点设置样式的方法和代码,样本点可以设置成任何颜色,以及7种形状,以便更直观了解数据的分布和特征。 文章目录 一、统一设置样式1.1 示例代码1.2 示例代码链接二、每一类一个样式2.1 示例代码2.2…

React - 你使用过高阶组件吗

难度级别:初级及以上 提问概率:55% 高阶组件并不能单纯的说它是一个函数,或是一个组件,在React中,函数也可以做为一种组件。而高阶组件就是将一个组件做为入参,被传入一个函数或者组件中,经过一定的加工处理,最终再返回一个组件的组合…

【C语言】结构体、枚举、联合(自定义类型)

文章目录 前言一、结构体1.结构体的声明2.结构体的自引用3.结构体变量的定义和初始化4.结构体成员的访问5.结构体内存对齐&#xff08;重点&#xff09;6.#pragma修改默认对齐数7.结构体传参 二、位段1.位段的声明2.位段的内存分配3.位段的跨平台问题 三、枚举四、联合 &#x…

Vite+Vue3.0项目使用ant-design-vue <a-calendar>日期组件汉化

antd的弹框、日期等默认为英文&#xff0c;要把英文转为中文请看下文&#xff1a; 1.首先我们要在main.js中引入ant-design组件库并全局挂载&#xff1a; import App from ./App import Antd from ant-design-vue; import ant-design-vue/dist/antd.css;const app createApp(…

备考ICA----Istio实验18---单集群中部署多个Istio控制面

备考ICA----Istio实验18—单集群中部署多个Istio控制面 单个 Kubernetes 控制面以及多个 Istio 控制面和多个网格。通过 Kubernetes 命名空间和 RBAC 实现软多租户业务隔离。 1. 环境准备 1.1 创建2个命名空间 kubectl create ns usergroup-1 kubectl label ns usergroup-…