Java笔记:看清类加载过程

news2024/12/23 11:01:51

1 类加载的过程

1.1 加载

“加载”是“类加载”(Class Loading)过程的第一步。这个加载过程主要就是靠类器实现的,包括用户自定义类加载器。

加载的过程
在加载的过程中,JVM主要做3件事情
1)通过一个类的全限定名来获取定义此类的二进制字节流(class文件)在程序运行过程中,当要访问一个类时,若发现这个类尚未被加载,并满足类初始化的条件时,就根据要被初始化的这个类的全限定名找到该类的二进制字节流,开始加载过程

2)将这个字节流的静态存储结构转化为方法区的运行时数据结构

3)在内存中创建一个该类的java. lang. Class对象,作为方法区该类的各种数据的访问入口
程序在运行中所有对该类的访问都通过这个类对象,也就是这个Class对象是提供给外界访问该类的接口。

加载源
JVM规范对于加载过程给予了较大的宽松度.一般二进制字节流都从已经编译好的本地class文件中读取,此外还可以从以下地方读取。
1)zip包
​ Jar、War、Ear等
2)其它文件生成
​ 由JSP文件中生成对应的Class类.
3)数据库中
​ 将二进制字节流存储至数据库中,然后在加载时从数据库中读取.有些中间件会这么做,用来实现代码在集群间分发
4)网络
​ 从网络中获取二进制字节流.典型就是Applet.
5)运行时计算生成
​ 动态代理技术,用ProxyGenerator.generateProxyClass为特定接口生成形式为"*$Proxy"的代理类的二进制字节流.

类和数组加载的区别
数组也有类型,称为“数组类型”.如:

String[] str = new String[10];

这个数组的数组类型是[Ljava.lang.String ,而String只是这个数组的元素类型。

数组类和非数组类的类加载是不同的,具体情况如下:
非数组类: 是由类加载器来完成。
数组类: 数组类本身不通过类加载器创建,它是由java虚拟机直接创建,但数组类与类加载器有很密切的关系,因为数组类的元素类型最终要靠类加载器创建。

加载过程的注意点
1)JVM规范并未给出类在方法区中存放的数据结构
类完成加载后,二进制字节流就以特定的数据结构存储在方法区中,但存储的数据结构是由虚拟机自己定义的,虚拟机规范并没有指定。

2)JVM规范并没有指定Class对象存放的位置
在二进制字节流以特定格式存储在方法区后,JVM会创建一个java.lang.Class类的对象,作为本类的外部访问接口。
既然是对象就应该存放在Java堆中,不过JVM规范并没有给出限制,不同的虚拟机根据自己的需求存放这个对象。
HotSpot将Class对象存放在方法区。

3)加载阶段和链接阶段是交叉的
类加载的过程中每个步骤的开始顺序都有严格限制,但每个步骤的结束顺序没有限制。也就是说,类加载过程中, 必须按照如下顺序开始:
加载 -> 链接 -> 初始化

但结束顺序无所谓,因此由于每个步骤处理时间的长短不一就会导致有些步骤会出现交叉。

1.2 验证

验证阶段比较耗时,它非常重要但不一定必要(因为对程序运行期没有影响),如果所运行的代码已经被反复使用和验证过,那么可以使用 -Xverify:none参数关闭,以缩短类加载时间。

验证的目的
保证二进制字节流中的信息符合虚拟机规范,并没有安全问题。

验证的必要性
虽然Java语言是一门安全的语言,它能确保程序猿无法访问数组边界以外的内存、避免让一个对象转换成任意类型、避免跳转到不存在的代码行.也就是说,Java语言的安全性是通过编译器来保证的.

但是我们知道,编译器和虚拟机是两个独立的东西,虚拟机只认二进制字节流,它不会管所获得的二进制字节流是哪来的,当然,如果是编译器给它的,那么就相对安全,但如果是从其它途径获得的,那么无法确保该二进制字节流是安全的。

通过上文可知,虚拟机规范中没有限制二进制字节流的来源,在字节码层面上,上述Java代码无法做到的都是可以实现的,至少语义上是可以表达出来的,为了防止字节流中有安全问题,需要验证!

验证的过程

1)文件格式验证
验证字节流是否符合Class文件格式的规范,并且能被当前的虚拟机处理.
本验证阶段是基于二进制字节流进行的,只有通过本阶段验证,才被允许存到方法区
后面的三个验证阶段都是基于方法区的存储结构进行,不会再直接操作字节流。

印证【加载和验证】是交叉进行的:
1.加载开始前,二进制字节流还没进方法区,而加载完成后,二进制字节流已经存入方法区
2.而在文件格式验证前,二进制字节流尚未进入方法区,文件格式验证通过之后才进入方法区也就是说,加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作

2)元数据验证
对字节码描述信息进行语义分析,确保符合Java语法规范.

3)字节码验证
本阶段是验证过程的最复杂的一个阶段。

本阶段对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件。

字节码验证将对类的方法进行校验分析,保证被校验的方法在运行时不会做出危害虚拟机的事,一个类方法体的字节码没有通过字节码验证,那一定有问题,但若一个方法通过了验证,也不能说明它一定安全。

4)符号引用验证
发生在JVM将符号引用转化为直接引用的时候,这个转化动作发生在解析阶段,对类自身以外的信息进行匹配校验,确保解析能正常执行。

1.3 准备

仅仅为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即零值,这里不包含用final修饰的static,因为final在编译的时候就会分配了(编译器的优化),同时这里也不会为实例变量分配初始化。类变量会分配在方法区中, 而实例变量是会随着对象一起分配到Java堆中。

准备阶段主要完成两件事情:
1)为已在方法区中的类的静态成员变量分配内存
2)为静态成员变量设置初始值, 初始值为0、 false、 null等

比如:

public static int x = 1000;

注意:
实际上变量x在准备阶段过后的初始值为0,而不是1000
将x赋值为1000的putstatic指令是程序被编译后,存放于类构造器方法之中

但是如果声明为:
public static final int x = 1000;
在编译阶段会为x生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将x赋值为1000。

1.4 解析

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

解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info四种常量类型。

1. 类或接口的解析:
判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。

2. 字段解析:
对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束(优先从接口来,然后是继承的父类.理论上是按照上述顺序进行搜索解析,但在实际应用中,虚拟机的编译器实现可能要比上述规范要求的更严格一些。如果有一个同名字段同时出现在该类的接口和父类中,或同时在自己或父类的接口中出现,编译器可能会拒绝编译).

3. 类方法解析:
对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。

4. 接口方法解析:
与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。

1.5 初始化

初始化是类加载过程的最后一步,到了此阶段,才真正开始执行类中定义的Java程序代码(初始化成为代码设定的默认值)。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器()方法的过程。

其实初始化过程就是调用类初始化方法的过程,完成对static修饰的类变量的手动赋值还有主动调用静态代码块(如果这两个不存在的话,就没有初始化过程)。

初始化过程的注意点:
1)方法是编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的.
2)静态代码块只能访问到出现在静态代码块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问.

public class Test {
    static {
        i=0; 
        System.out.println(i);//编译失败:"非法向前引用" 
    }
    static int i = 1;
}

3)实例构造器需要显式调用父类构造函数,而类的不需要调用父类的类构造函数,虚拟机会确保子类的方法执行前已经执行完毕父类的方法.因此在JVM中第一个被执行的方法的类肯定是java.lang.Object.

4)如果一个类/接口中没有静态代码块,也没有静态成员变量的赋值操作,那么编译器就不会为此类生成方法.

5)接口也需要通过方法为接口中定义的静态成员变量显示初始化。
接口中不能使用静态代码块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成方法.不同的是,执行接口的方法不需要先执行父接口的方法.只有当父接口中的静态成员变量被使用到时才会执行父接口的方法.

6)虚拟机会保证在多线程环境中一个类的方法别正确地加锁,同步.当多条线程同时去初始化一个类时,只会有一个线程去执行该类的方法,其它线程都被阻塞等待,直到活动线程执行方法完毕.其他线程虽会被阻塞,只要有一个方法执行完,其它线程唤醒后不会再进入方法.同一个类加载器下,一个类型只会初始化一次.

使用静态内部类的单例实现:

public class Student {
    private Student() {}
    /*
    * 此处使用一个内部类来维护单例 JVM在类加载的时候,是互斥的,所以可以由此保证线
    程安全问题
    */
    private static class SingletonFactory {
        private static Student student = new Student();
    }
    /* 获取实例 */
    public static Student getSingletonInstance() {
        return SingletonFactory.student;
    }
}

2. 类加载的时机

什么时候开始加载,虚拟机规范并没有强制性的约束,对于其它大部分阶段究竟何时开始虚拟机规范也都没有进行规范,这些都是交由虚拟机的具体实现来把握。所以不同的虚拟机它们开始的时机可能是不同的。但是对于初始化却严格的规定了有且只有四种情况必须先对类进行“初始化”(加载,验证,准备自然需要在初始化之前完成):

  1. 遇到new、getstatic、putstatic和invokestatic这四条指令时,如果对应的类没有初始化,则要对对应的类先进行初始化。

这四个指令对应到我们java代码中的场景分别是:

  • new关键字实例化对象的时候;
  • 读取或设置一个类的静态字段(读取被final修饰,已在编译器把结果放入常量池的静态字段除外) ;
  • 调用类的静态方法时。
  1. 使用java.lang.reflect包方法时对类进行反射调用的时候。

  2. 初始化一个类的时候发现其父类还没初始化,要先初始化其父类。

  3. 当虚拟机开始启动时,用户需要指定一个主类,虚拟机会先执行这个主类的初始化。

3. 类加载器

启动类加载器(Bootstrap ClassLoader):

  • 负责加载 JAVA_HOME\lib 目录中的,
  • 或通过-Xbootclasspath参数指定路径中的,
  • 且被虚拟机认可(按文件名识别,如rt.jar)的类。
  • 由C++实现,不是ClassLoader子类
    扩展类加载器(Extension ClassLoader):
  • 负责加载 JAVA_HOME\lib\ext 目录中的,
  • 或通过java.ext.dirs系统变量指定路径中的类库。
    应用程序类加载器(Application ClassLoader):
  • 负责加载用户路径(classpath)上的类库。

JVM的类加载是通过ClassLoader及其子类来完成的, 类的层次关系和加载顺序可以由下图来描述:

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrapClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

3.1 自定义类加载器

自定义类加载器步骤
(1)继承ClassLoader
(2)重写findClass()方法
(3)调用defineClass()方法

实践
下面写一个自定义类加载器:指定类加载路径在D盘下的lib文件夹下。
(1)在本地磁盘新建一个 Test.java类,代码如下:

package jvm.classloader;
public class Test {
    public void say(){
        System.out.println("Hello MyClassLoader");
    }
}

(2)使用 javac -d . Test.java命令,将生成的Test.class文件放到D:/lib/jvm/classloader文件夹下。

(3)在Eclipse中自定义类加载器,代码如下:

package jvm.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
    
    private String classpath;
    
    public MyClassLoader(String classpath) {
        this.classpath = classpath;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        try {
            byte [] classDate=getData(name);
            if(classDate==null){}
            else{
                //defineClass方法将字节码转化为类
                return defineClass(name,classDate,0,classDate.length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }
    
    //返回类的字节码
    private byte[] getData(String className) throws IOException{
        InputStream in = null;
        ByteArrayOutputStream out = null;
        String path=classpath + File.separatorChar +
        className.replace('.'File.separatorChar)+".class";
        try {
            in=new FileInputStream(path);
            out=new ByteArrayOutputStream();
            byte[] buffer=new byte[2048];
            int len=0;
            while((len=in.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            return out.toByteArray();
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally{
            in.close();
            out.close();
        }
        return null;
    }
}

测试代码如下(在Eclipse中):

package jvm.classloader;
import java.lang.reflect.Method;
public class TestMyClassLoader {
    public static void main(String []args) throws Exception{ 
        //自定义类加载器的加载路径
        MyClassLoader myClassLoader=new MyClassLoader("D:\\lib"); 
        //包名+类名
        Class c=myClassLoader.loadClass("jvm.classloader.Test");

        if(c!=null){
            Object obj=c.newInstance();
            Method method=c.getMethod("say"null); 
            method.invoke(obj, null); 
            System.out.println(c.getClassLoader().toString()); 
        }
    }
}    

输出结果如下:

自定义类加载器的作用:
JVM自带的三个加载器只能加载指定路径下的类字节码。
如果某个情况下,我们需要加载应用程序之外的类文件呢?比如本地D盘下的,或者去加载网络上的某个类文件,这种情况就可以使用自定义加载器了

4. 双亲委派模型

JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。
1)当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,
2)只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。

采用双亲委派的一个好处是:
比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。

为什么要使用双亲委托这种模型呢?
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器( Bootstrcp ClassLoader) 加载, 所以用户自定义的ClassLoader永远也无法加载一个自己写的String, 除非你改变JDK中ClassLoader搜索类的默认算法。

但是JVM在搜索类的时候, 又是如何判定两个class是相同的呢?
JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。

只有两者同时满足的情况下,JVM才认为这两个class是相同的。就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。

既然JVM已经提供了默认的类加载器, 为什么还要定义自已的类加载器呢?
因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时。

比如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader

5. 破坏双亲委派模型

为什么需要破坏双亲委派?
因为在某些情况下父类加载器需要加载的class文件由于受到加载范围的限制,父类加载器无法加载到需要的文件,这个时候就需要委托子类加载器进行加载。

而按照双亲委派模式的话,是子类委托父类加载器去加载class文件。这个时候需要破坏双亲委派模式才能加载成功父类加载器需要的类。也就是说父类会委托子类去加载它需要的class文件。

以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了 MySQL Connector,这些实现类都是以jar包的形式放到classpath目录下。

那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类(classpath下),然后进行管理,但是DriverManager由启动类加载器加载,只能加载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。(让本来由启动类加载器加载Driver改为由应用程序加载器加载Driver类,原因:DriverManager出现在Java1.0,双亲委派出现在1.2,为了兼容老版本,所以出现了破坏双亲委派模型)

JDBC代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
public class TestJdbc {
  @Test
    public void testJdbc() {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet rs = null;
    try {
      // 加载数据库驱动
      Class.forName("com.mysql.jdbc.Driver");
      // 通过驱动管理类获取数据库链接connection = DriverManager
      connection =
           DriverManager.getConnection("jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8""root""root");
      // 定义sql语句 ?表示占位符
      String sql = "select * from user where id = ?";
      // 获取预处理 statement
      preparedStatement = connection.prepareStatement(sql);
      // 设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为 设置的
      preparedStatement.setint(11);
      // 向数据库发出 sql 执行查询,查询出结果集 rs = preparedStatement.executeQuery(); // 遍历查询结果集
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    finally {
      // 释放资源 if (rs != null) { try { rs.close();
    }
    catch (SQLException e) {
      e.printStackTrace();
    }
  }
  if (preparedStatement != null) {
    try {
      preparedStatement.close();
    }
    catch (SQLException e) {
      e.printStackTrace();
    }
  }
  if (connection != null) {
    try {
      connection.close();
    }
    catch (SQLException e) {
    }
  }
}
}
}

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

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

相关文章

【Linux网络编程】日志与守护进程

日志是网络服务器程序在后台以守护进程的形式运行时&#xff0c;处理情况的描述被打印到了日志文件里面&#xff0c;方便维护人员查看。 1.前台进程与后台进程 左边会话输入命令 sleep 10000 & 代表进程后台运行&#xff0c;右边会话输入命令 sleep 20000可以看到命令行解…

珠海建筑模板厂家-能强优品木业:为您提供优质建筑模板解决方案

在珠海这座美丽的沿海城市&#xff0c;建筑行业蓬勃发展&#xff0c;对于高质量的建筑模板需求也日益增加。在这里&#xff0c;有一家备受赞誉的建筑模板厂家&#xff0c;那就是能强优品木业。作为一家专业的建筑模板供应商&#xff0c;他们以优质的产品和卓越的服务在业界享有…

联合查询

1.条件 2.步骤 1.判断列数 2.判断回显 3.重要 在回显位置写 查看字段名字 使用工具 hackbar

C语言while循环嵌套-动态字母

1、题目 使用C语言实现对字母动态移动&#xff08;根据用户输入的字符将字符从屏幕的坐标移动屏幕的右边&#xff09;。 2、分析 字符的移动核心是在显示的字母前面补上对应的空格字符内容&#xff0c;配合上延时就可以实现字符从屏幕左边移动到屏幕右侧的效果&#xff0c;实现…

【全网最全】2023华为杯研究生数学建模B题完整思路+python代码+20页超详细启发式算法+FFT(后续会更新)

目录 点击资料获取入口 DFT在通信等领域的重要应用,以及目前采用FFT计算DFT的硬件开销大的问题。提出了将DFT矩阵分解为整数矩阵乘积逼近的方法来降低硬件复杂度。 建模目标是对给定的DFT矩阵F_N,找到一组K个矩阵A,使F_N和A的乘积在Frobenius范数意义下尽可能接近,即最小化目标…

开源负载测试神器K6

简介&#xff1a;K6是一个强大的开源负载和性能测试工具&#xff0c;用于测试软件系统的性能和可靠性。K6的使用主要是编写测试脚本并运行&#xff0c;这些脚本主要用JavaScript编写&#xff0c;可以使用HTTP&#xff0c;WebSocket等多种协议进行测试。并且易于安装和运行&…

Java Web框架,如Spring MVC,是一种用于构建Web应用程序的软件框架:学生考试Web应用程序

文章目录 什么是Java Web框架&#xff1f;MVC模式在Spring MVC中的应用简单的学生考试Web应用程序设置Spring MVC项目创建实体类创建考试实体类创建控制器创建服务层创建数据库创建视图配置Spring MVC实现功能运行应用程序运行应用程序 &#x1f388;个人主页&#xff1a;程序员…

MySQL学习笔记3

MySQL的源码编译安装&#xff1a; 1、参考MySQL的源码安装官方文档&#xff1a; 2、源码安装定制选项&#xff1a; 3、源码安装三部曲&#xff1a;配置、编译、安装。 4、软件安装包&#xff1a; mysql-boost-5.7.43.tar.gz 5、安装需求&#xff1a; 安装需求具体配置安装目…

安装gpu版本的paddle和paddleclas

安装gpu版本的paddle python -m pip install paddlepaddle-gpu2.3.2.post111 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html以上支持cuda11.1版本 其他需求可查阅文档在这里 安装paddleclas 1 在虚拟环境中安装所需的Python库&#xff1a; pip inst…

Cortex-M3/M4堆栈

一、Cortex-M3/M4堆栈操作 Cortex-M3/M4 使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32 位数值。在下一次压栈时&#xff0c; SP 先自减 4&#xff0c; 再存入新的数值&#xff0c;如图所示为堆栈的PUSH操作。 POP 操作刚好相反&#xff1a;先从 …

电子信息工程专业课复习知识点总结:(五)通信原理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 第一章通信系统概述——通信系统的构成、各部分性质、性能指标1.通信系统的组成&#xff1f;2.通信系统的分类&#xff1f;3.调制、解调是什么&#xff1f;有什么用…

MySQL详解六:备份与恢复

文章目录 1. 数据库备份的分类1.1 从物理和逻辑上分类1.1.1 物理备份1.1.2 逻辑备份 1.2 从数据库的备份策略角度上分类1.2.1 完全备份1.2.2 差异备份1.2.3 增量备份 1.3 常见的备份方法 2. MySQL完全备份2.1 完全备份简介2.2 优点与缺点2.3 实现物理冷备份与恢复2.3.1 实现流程…

备受以太坊基金会青睐的 Hexlink,构建亿级用户涌入 Web3的入口

早在2021年9月&#xff0c;以太坊创始人Vitalik Buterin就曾提出了EIP-4337&#xff08;账户抽象&#xff09;提案&#xff0c;并在去年10月对该提案进一步更新&#xff0c;引发行业的进一步关注。在今年3月&#xff0c;EIP-4337提案正式通过审计&#xff0c;并成为了ERC-4337标…

conda常用指令

常用conda指令 查看当前有哪些环境&#xff0c;有base环境 conda env list 创建环境 # conda create -n 你的环境名 python版本号 # 创建python3.10&#xff0c;名为env虚拟环境 conda create -n env python3.10 激活环境 conda activate env

java框架-Spring-AOP

AOP:动态代理 开发步骤&#xff1a; 导入aop模块定义业务逻辑类定义切面类&#xff1b; -. 切面类标注&#xff1a;Aspect -. 切面类注解&#xff1a; Before: 前置通知, 在方法执行之前执行 After: 后置通知, 在方法执行之后执行 。 AfterRunning: 返回通知, 在方法返回结果之…

[杂谈]-快速了解半波和全波整流

快速了解半波和全波整流 文章目录 快速了解半波和全波整流1、滤波2、半波整流器3、全波整流器4、常见问题 整流器是一种将交流信号转换为脉动直流信号以及将交流电转换为直流电的电子电路。 我们日常生活中几乎所有的电子项目都会用到它。 根据周期传导&#xff0c;本文我们介绍…

【Python】ModuleNotFoundError: No module named ‘Crypto‘

今天在使用一个新的库Crypto时发生了报错 Crypto安装成功~ 导入Crypto模块也没有问题 运行时却发生了报错&#xff1a; 没有这个模块&#xff1f; 我明明安装成功了&#xff0c;为什么报错没有这个库呢&#xff1f; 于是我去查看了一下是不是没有安装上呢&#xff1f; 为什么…

sql注入挖掘

出现的条件 只要是和数据库有交互 没有过滤拼接的sql语句可以执行 判断 这个是在url筐里的

想要精通算法和SQL的成长之路 - 双指针【数组】

想要精通算法和SQL的成长之路 - 双指针【数组】 前言一. 合并两个有序数组二. 删除有序数组中的重复项 II 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 合并两个有序数组 原题链接 抓住重点信息&#xff1a; 两个数组都是非递减顺序排列。num1数组&#xff0c;末尾包…

在Bat To Exe Converter,修改为当异常结束或终止时,程序重新启动执行

在Bat To Exe Converter&#xff0c;修改为当异常结束或终止时&#xff0c;程序重新启动执行 .bat中的代码部分&#xff1a; .bat中的代码echo offpython E:\python\yoloProjectTestSmallLarge\detect.pypause&#xff0c;我想你能帮在Bat To Exe Converter&#xff0c;修改成…