JVM学习(六):类加载子系统

news2024/9/21 2:39:55

目录

〇、前言 

一、类加载子系统

1.1 内存结构概述 

1.2 类加载器及类加载过程概述 

1.2.1 类加载器 

1.2.2 类加载过程 

1.3 类加载过程一:Loading

1.3.1 加载过程

1.3.2 加载类的方式

1.4 类加载过程二:Linking

1.4.1 验证(Verify) 

1.4.2 准备(Prepare) 

1.4.3 解析(Resolve) 

1.5 类加载过程三:Initialization

二、类加载器

2.1 类加载器分类 

2.2 启动类加载器(引导类加载器,BootstrapClassLoader)

2.3 扩展类加载器(ExtensionClassLoader)

2.4 应用程序类加载器(系统类加载器,AppclassLoader)

2.5 用户自定义类加载器(UserDefinedClassLoader)

2.5.1 为什么要自定义类加载器

2.5.2 用户自定义类加载器实现步骤

 2.6 ClassLoader

2.6.1 常用方法

2.6.2 获取ClassLoader的途径

三、双亲委派机制

3.1 简介 

3.2 工作原理

3.3 代码验证

3.4 双亲委派机制的优点 

3.5 沙箱安全机制

3.6 两个Class对象是否为同一个类


〇、前言 

        如果为了面试而来,那么建议看我的这篇文章:Java面试必问:类加载过程与类加载器_玉面大蛟龙的博客-CSDN博客 

        如果是想深究JVM,那么欢迎你往下看: 

        本篇文章介绍的就是下图黄色区域的部分。 

一、类加载子系统

1.1 内存结构概述 

1.2 类加载器及类加载过程概述 

1.2.1 类加载器 

  • 类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识。
  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由ExecutionEngine决定。
  • 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)

1.2.2 类加载过程 

 

        以一个类HelloLoader.java为例: 

1.3 类加载过程一:Loading

1.3.1 加载过程

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

1.3.2 加载类的方式

  • 从本地系统中直接加载
  • 通过网络获取,典型场景: web Applet
  • 从zip压缩包中读取,成为区后jar、 war格式的基础运行时计算生成,使用最多的是:动态代理技术
  • 由其他文件生成,典型场景:JSP应用(由jsp文件生成对应的class文件)
  • 从专有数据库中提取.class文件,比较少见
  • 从加密文件中获取,典型的防class文件被反编译的保护措施

1.4 类加载过程二:Linking

         Linking链接过程又分为三个子阶段:

1.4.1 验证(Verify) 

        经过loading阶段后,已经能够在内存当中生成class实例了。Verify的目的在子确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。

        主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

        例如,所有能被Java虚拟机识别的字节码文件,都必须以CAFEBABE开头(是咖啡宝贝的意思)。

  

1.4.2 准备(Prepare) 

        Prepare阶段为类变量分配内存并且设置该类变量的默认初始值,即零值。

        这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;这里也不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

        例如:下面代码中的a变量,先在准备阶段中被赋值为0,再在初始化阶段被赋值为1。

public class HelloApp {
    //prepare:a = 0 ---> initial : a = 1
    private static int a = 1;


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

1.4.3解析(Resolve) 

        Resolve阶段是将常量池内的符号引用转换为直接引用的过程。

        事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。

        符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机
规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

        解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的
CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT Methodref_info等。

        还是以上述HelloApp类代码为例,这个类看似代码量很少,但其实在准备阶段需要加载很多的类,将这些类都放在class文件中是不现实的。所以写成符号引用,在准备阶段再转换为直接引用。

        咱们可以看一下有哪些引用。反编译一下HelloApp的字节码文件:在 HelloApp.class所在目录下输入命令:

javap -v .\HelloApp.class

         可以看到加载了很多的类信息,这些类信息都需要在解析阶段转为直接引用。

1.5 类加载过程三:Initialization

        初始化阶段就是执行类构造器方法<clinit>()的过程。此方法不需定义,是javac编译器自动收集类中的 所有类变量的赋值动作 和 静态代码块中的语句 合并而来。构造器方法中指令按语句在源文件中出现的顺序执行。

        <clinit> ()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>())。若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。

        虚拟机必须保证一个类的<clinit> ()方法在多线程下被同步加锁。

        下面我们用一些例子来说明这些:        

        例如,我们编写一个简单的类来看看是否有<clinit>方法: 

public class ClassInitTest {

    private static int num = 1;

    public static void main(String[] args) {
        System.out.println(ClassInitTest.num);//2
    }
}

        使用jclasslib Bytecode Viewer 插件查看,发现除了我们手写的main方法之外,还有<init>和<clinit>两个自动生成的方法。

        <init>方法待会再说,我们查看<clinit>方法的字节码,发现就是将变量赋值为1:

        加一个静态代码块的初始化:

public class ClassInitTest {

    private static int num = 1;

    static{
       num = 2;
   }

    public static void main(String[] args) {
        System.out.println(ClassInitTest.num);//2
    }
}

         再次查看,发现是先赋值为1,再变为2 

        这说明了,<clinit>初始化是将显式初始化静态代码块的初始化 合并在一起构成了初始化。并且,构造器方法中指令按语句在源文件中出现的顺序执行。

        为了更清楚地说明顺序执行的问题,我们再将代码改的复杂一点。我们再定义一个number值,将定义的语句放在静态代码块的下面,看看结果:

public class ClassInitTest {

   private static int num = 1;

   static{
       num = 2;
       number = 20;
       System.out.println(num);
       //System.out.println(number);//报错:非法的前向引用。
   }

   private static int number = 10;  //linking之prepare: number = 0 --> initial: 20 --> 10

    public static void main(String[] args) {
        System.out.println(ClassInitTest.num);//2
        System.out.println(ClassInitTest.number);//10
    }
}

        看字节码也可以得知,number变量先被赋值为20,然后又赋值为10

         需要注意的是,我们虽然可以在定义number的语句之前的静态代码块中给number赋值,但却不能调用它:

         同时需要注意的是,如果没有static变量赋值或static代码块赋值,将不会执行<clinit>方法。例如下面代码:

public class ClinitTest {

    private int a = 1;

    public static void main(String[] args) {
        int b = 2;
    }
}

        没有<clinit>方法 

         再来看看父类和子类的<clinit>() 执行顺序。编写以下代码:

public class ClinitTest1 {
    static class Father{
        public static int A = 1;
        static{
            A = 2;
        }
    }

    static class Son extends Father{
        public static int B = A;
    }

    public static void main(String[] args) {
        //加载Father类,其次加载Son类。
        System.out.println(Son.B);//2
    }
}

         查看一下<clinit>()方法的字节码:

         可以看到是先加载的父类,然后再将父类的值赋给子类。

         最后再来看看一个类的<clinit> ()方法是否在多线程下被同步加锁。在下面的代码中,我们开启两个线程,都实例化DeadThread类,而DeadThread的static代码块中是一个死循环。那么,如果<clinit>()方法被同步加锁,我们应该只能看到一次初始化的打印信息。

public class DeadThreadTest {
    public static void main(String[] args) {
        Runnable r = () -> {
            System.out.println(Thread.currentThread().getName() + "开始");
            DeadThread dead = new DeadThread();
            System.out.println(Thread.currentThread().getName() + "结束");
        };

        Thread t1 = new Thread(r,"线程1");
        Thread t2 = new Thread(r,"线程2");

        t1.start();
        t2.start();
    }
}

class DeadThread{
    static{
        if(true){
            System.out.println(Thread.currentThread().getName() + "初始化当前类");
            while(true){

            }
        }
    }
}

 

        确实只初始化了一次,说明<clinit>()方法被JVM同步加锁了。所以,如果一个类在加载的时候出现问题,那么会导致其他想要加载的代码全部阻塞等待,我们要特别小心这种问题。 

二、类加载器

2.1 类加载器分类 

        JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和 自定义类加载器(User-Defined classLoader)

        从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器(包括ExtensionClassLoader 和 AppclassLoader)。

        无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示:

        需要注意的是:这里的四者之间的关系是包含关系。不是子父类的继承关系。就好像文件路径/a/b/c/d.txt,我们不能说b继承于a。

        我们在代码中实际体会一下: 

public class ClassLoaderTest {
    public static void main(String[] args) {

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取系统类加载器的上层:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        //获取扩展类加载器的上层:获取不到引导类加载器(其实是引导类加载器BootstrapClassLoader,他是用C/C++语音写的,我们无法获取)
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null
    }
}

2.2 启动类加载器(引导类加载器,BootstrapClassLoader

  • 它是使用C/C++语言实现的,嵌套在JVM内部
  • 它用来加载Java的核心库(JAVA_ HOME/jre/ lib/rt.jar、resources.jar或sun. boot.class.path路径下的内容),用于提供JVM自身需要的类
  • 并不继承自java.lang.classLoader,没有父加载器
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类

2.3 扩展类加载器(ExtensionClassLoader)

  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

2.4 应用程序类加载器(系统类加载器,AppclassLoader)

  • java语言编写,由sun.misc.Launcher$AppclassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器
  • 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
  • 通过ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器

         我们再通过一段代码加深一下理解:

public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("**********启动类加载器**************");
        //获取BootstrapClassLoader能够加载的api的路径
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL element : urLs) {
            System.out.println(element.toExternalForm());
        }
        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);    //null

        System.out.println("***********扩展类加载器*************");
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path : extDirs.split(";")) {
            System.out.println(path);
        }

        //从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d

    }
}

        运行结果:

**********启动类加载器**************
file:/D:/Java/jdk/jdk1.8.0_161/jre/lib/resources.jar
file:/D:/Java/jdk/jdk1.8.0_161/jre/lib/rt.jar
file:/D:/Java/jdk/jdk1.8.0_161/jre/lib/sunrsasign.jar
file:/D:/Java/jdk/jdk1.8.0_161/jre/lib/jsse.jar
file:/D:/Java/jdk/jdk1.8.0_161/jre/lib/jce.jar
file:/D:/Java/jdk/jdk1.8.0_161/jre/lib/charsets.jar
file:/D:/Java/jdk/jdk1.8.0_161/jre/lib/jfr.jar
file:/D:/Java/jdk/jdk1.8.0_161/jre/classes
null
***********扩展类加载器*************
D:\java\jdk\jdk1.8.0_161\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@11d50c0

2.5 用户自定义类加载器(UserDefinedClassLoader)

        在Java的日常应用程序开发中,类的加载几乎都是由上述3种类加载器相互配合执行的。但在必要时,我们还可以自定义类加载器,来定制类的加载方
式。

2.5.1 为什么要自定义类加载器

  • 隔离加载类
  • 修改类加载的方式
  • 扩展加载源
  • 防止源码泄漏

2.5.2 用户自定义类加载器实现步骤

  1. 开发人员可以通过继承抽象类 java.lang.ClassLoaaer类的方式,实现自己的类加载器,以满足一些特殊的需求
  2. 在JDK1.2 之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass() 方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findclass()方法中
  3. 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继 URLClassLoader类,这样就可以避免自己去编写findclass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁

        通过代码简单体会一下:

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        try {
            byte[] result = getClassFromCustomPath(name);
            if(result == null){
                throw new FileNotFoundException();
            }else{
                return defineClass(name,result,0,result.length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        throw new ClassNotFoundException(name);
    }

    private byte[] getClassFromCustomPath(String name){
        //从自定义路径中加载指定类:细节略
        //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
        return null;
    }

    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One",true,customClassLoader);
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 2.6 ClassLoader

         ClassLoader类是一个抽象类,其后所有的类加载器都继承自ClassLoader (不包括启动类加载器)。

2.6.1 常用方法

        ClassLoader的常用方法都不是抽象方法。

方法名称描述
getParent()返回该类加载器的超类加载器
loadClass(String name)加载名称为name的类,返回结果为java.lang.Class类的实例
findClass(String name)查找名称为name的类,返回结果为java.lang.Class类的实例
findLoadedClass(String name)查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例
defineClass(String name, byte[] b, int off, int len)把字节数组b中的内容转换为一个Java类,返回结果为java.lang.Class类的实例
resolveClass(Class<?> c)

连接指定的一个Java类

 2.6.2 获取ClassLoader的途径

 

         通过代码简单实践一下:

public class ClassLoaderTest2 {
    public static void main(String[] args) {
        try {
            //1.
            ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
            System.out.println(classLoader);
            //2.
            ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
            System.out.println(classLoader1);

            //3.
            ClassLoader classLoader2 = ClassLoader.getSystemClassLoader().getParent();
            System.out.println(classLoader2);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

        运行结果:

null
sun.misc.Launcher$AppClassLoader@b4aac2
sun.misc.Launcher$ExtClassLoader@15fbaa4

三、双亲委派机制

3.1 简介 

        Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的
class文件时,Java虚拟机采用的是双亲委滤模式,即把请求交由父类处理,它是一种任务委派模式。

3.2 工作原理

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父柔的加载器去执行;
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  3. 如果父类加载器可以完成类加载任务,就成功返回;倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。这就是双亲委派模式。

3.3 代码验证

public class StringTest {

    public static void main(String[] args) {
        java.lang.String str = new java.lang.String();
        System.out.println("hello,atguigu.com");

        StringTest test = new StringTest();
        System.out.println(test.getClass().getClassLoader());
    }
}

        然后构建一个java.lang.String类 

public class String {

    static{
        System.out.println("我是自定义的String类的静态代码块");
    }
}

        如果我们自定义的String类被加载,控制台肯定能打印出上面这句话。但是运行后发现,并不能打印。说明JVM加载的还是自己的String类。 

        我们在自己的String类中写一个main方法并执行:

public class String {
    //
    static{
        System.out.println("我是自定义的String类的静态代码块");
    }
    //错误: 在类 java.lang.String 中找不到 main 方法
    public static void main(String[] args) {
        System.out.println("hello,String");
    }
}

        运行结果:

        说明JVM将运行java.lang.String类的需求一步步地递交到引导类加载器,发现他的String类中没有main方法。JVM压根就没有打算要加载我们自己的String方法。

3.4 双亲委派机制的优点 

  •  避免类的重复加载
  • 保护程序安全,防止核心API被随意篡改

 3.5 沙箱安全机制

        在刚刚的代码示例中,我们自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。 

 3.6 两个Class对象是否为同一个类

        在JVM中表示两个class对象是否为同一个类存在两个必要条件:

  • 类的完整类名必须一致,包括包名
  • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同

        换句话说,在JVM中,即使这两个类对象(Class对象)来源同一个class文件,被同一个虚拟机所加载,但只要加载它们的classLoader实例对象不同,那么这两个类对象也是不相等的。

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

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

相关文章

贯穿设计模式第八话--设计原则总结篇

&#x1f973;&#x1f973;&#x1f973; 茫茫人海千千万万&#xff0c;感谢这一刻你看到了我的文章&#xff0c;感谢观赏&#xff0c;大家好呀&#xff0c;我是最爱吃鱼罐头&#xff0c;大家可以叫鱼罐头呦~&#x1f973;&#x1f973;&#x1f973; 从今天开始&#xff0c;将…

在uos上编译opencv

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 下载源码并创建build文件夹 系统环境为操作系统为&#xff1a;UnionTech OS Server 20 Enterprise&#xff0c;处理器为: 华为鲲鹏处理器&#xff08;ar…

C++11(上)

目录 1&#xff1a;列表初始化 2&#xff1a;std::initializer_list 3:变量类型推导 3.1:auto推导类型 3.2:decltype 3.3:nullptr 4:范围for 5:STL新增容器和容器新增接口 5.1:array 6:左值引用和右值引用 6.1:左值 6.2:右值 6.3:左值引用 6.4:右值引用 6.5:左值…

python常用库之time库

目录 一、前言time库中的常用函数 二、time()函数三、localtime()和gmtime()函数四、strftime() 、asctime()、mktime()函数&#xff08;一&#xff09;strftime()函数&#xff08;二&#xff09;asctime()函数&#xff08;三&#xff09;mktime()函数 五、ctime()函数六、stri…

【2023最新】超详细图文保姆级教程:App开发新手入门(5)

上文回顾&#xff0c;我们已经完成了一个应用的真机调试&#xff0c;本章我们来了解一下如何引入YonBuilder移动开发的&#xff08;原生&#xff09;移动插件, 并利用移动插件完成一个简单的视频播放器。 8. 「移动插件」的使用 8.1 什么是 「移动插件」&#xff1f; 用通俗…

TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:1~5

原文&#xff1a;Mobile Deep Learning with TensorFlow Lite, ML Kit and Flutter 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的…

【高危】Apache Spark 权限提升漏洞(CVE-2023-22946)

漏洞描述 Apache Spark 是一款支持非循环数据流和内存计算的大规模数据处理引擎。 使用 spark-submit 脚本在集群中启动任务可以通过指定proxy-user参数限制运行用户。在 Apache Spark 受影响版本中&#xff0c;攻击者通过指定自定义的classpath&#xff0c;则可以覆盖该配置…

2023最新面试题-Java-3

IO流 1. java 中 IO 流分为几种? 按照流的流向分&#xff0c;可以分为输入流和输出流&#xff1b;按照操作单元划分&#xff0c;可以划分为字节流和字符流&#xff1b;按照流 的角色划分为节点流和处理流。 Java Io 流共涉及 40 多个类&#xff0c;这些类看上去很杂乱&…

ChatGPT 70+款可以免费使用的AI工具,建议收藏

ChatGPT风靡全球&#xff0c;人人可用&#xff01; 小红书上有关ChatGPT的笔记已有10w篇&#xff0c;相关话题浏览量也达到了1.12亿次。其中讨论最为热烈的&#xff0c;要数“ChatGPT使用教程”。&#xff08;当然&#xff0c;类似的话题还包括&#xff0c;教你如何使用Midjour…

Navicat图表查看器 Crack

Navicat图表查看器 Crack Navicat图表查看器是一个查看图表工作区文件的简单工具。您可以浏览Navicat的图表工具和Navicat图表创建者创建的区域。 Navicat图表查看器&#xff0c;将图表中的数据显示为强大的可视化效果&#xff0c;允许您使用图形和图表查看数据。 将您的信息转…

Internet Download Manager(IDM)v6.41.11 免激活不弹窗版

Internet Download Manager&#xff08;IDM&#xff09;v6.41.11 免激活不弹窗版可提升你的下载速度多达5倍&#xff0c;安排下载时程&#xff0c;或续传一半的软件。Internet Download Manager的续传功能可以恢复因为断线、网络问题、计算机宕机甚至无预警的停电导致下传到一半…

大数据开发必备面试题Flume篇合集

大数据开发必备面试题Flume篇合集 1 、详细介绍Flume有哪些组件&#xff1f;2、你是如何实现Flume数据传输的监控的&#xff1f;3、Flume参数怎么调优&#xff1f;4、简述下Flume的事务机制。5、 Flume采集数据会丢失吗?6、简述下Flume使用场景。7、简述下 Flume丢包问题。8、…

【C语言】文件操作

目录 1.为什么使用文件 2.什么是文件 2.1 程序文件 2.2 数据文件 2.3文件名 3.文件的打开和关闭 3.1文件指针 3.2文件的打开和关闭 4.程序的顺序读写 4.2对比一组函数 5.文件的随机读写 5.1 fseek 5.2 ftell 5.3 rewind 6.文本文件和二进制文件 7.文件读取结束的…

2023年银行理财子公司研究报告

第一章 行业发展概况 1.1 行业概况 所谓“银行理财子公司”&#xff0c;其实就是由商业银行作为控股股东发起设立的&#xff0c;并经国务院银行业监督管理机构批准&#xff1b;主要从事理财业务&#xff1b;独立于母行&#xff0c;具有独立法人地位的非银行金融机构。像工商银…

Redission分布式锁

实现过程&#xff1a; 只要线程一加锁成功&#xff0c;就会启动一个 watch dog 看门狗&#xff0c;它一个后台线程&#xff0c; 会每隔 10 秒检查一下&#xff0c;如果线程 1 还持有锁&#xff0c;那么就会不断延长锁 key 生存时间。因此&#xff0c;Redisson 解决了锁过期释放…

Commitizen规范git提交代码

首先全局安装Commitizen&#xff0c;运行&#xff1a; npm install -g commitizen 然后在项目中开启终端&#xff0c;安装cz-customizable npm i cz-customizable --save-dev 然后在package.json中配置如下代码&#xff1a; "config": {"commitizen":…

虹科干货| 虹科Redis企业版数据库:告别游戏卡顿,让快乐加速!

“卡顿一分钟&#xff0c;玩家两行泪” 游戏已成为年轻人最主要的消遣娱乐方式之一&#xff0c;游戏卡顿给玩家带来糟糕游戏体验背后的原因是什么&#xff1f;数据存储与查询速度不够快&#xff01; 游戏开发领域&#xff0c;不仅拥有海量的数据&#xff0c;甚至还要做到实时…

Flowable6.x导出/查看/跟踪流程图

Flowable6.x导出/查看/跟踪流程图 项目源码仓库 Flowable诞生于Activiti&#xff0c;是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义&#xff0c;可以十分灵活地加入你的应用/服务/构架。 本文介绍4种绘制流程图的方式&#xff0c;前…

TryHackMe-Set(Windows渗透测试 | WinDefender免杀)

Set 您再次发现自己在Windcorp公司的内部网络上。上次你去那里的味道真好&#xff0c;你回来了 了解更多。 但是&#xff0c;这次他们设法保护了域控制器&#xff0c;因此您需要找到另一台服务器&#xff0c;并在第一次扫描时发现“Set”。 Set被用作开发人员的平台&#xf…

集团企业大数据治理解决方案word

第一章 集团企业大数据治理阶段目标 通过数据平台和BI应用建设&#xff0c;集团企业大数据将搭建统一的大数据共享和分析平台&#xff0c;对各类业务进行前瞻性预测及分析&#xff0c;为集团企业各层次用户提供统一的决策分析支持&#xff0c;提升数据共享与流转能力。 一.1、…