JVM 类加载器

字节码的结构

魔数u4 cafe babe

版本u4 52 = java8

常量池计数器u2 从1开始,0索引留给不需要的情况

常量池 表 #1 -> #计数器-1

类标识符 u2 public final abstrat class annotion interface 之类

类索引u2 名字

父类索引u2 父类名字

接口计数器 u2 接口数组长度

接口集合 表 接口索引数组

字段计数器 u2

字段 表

-访问标识符 u2

-字段名索引 u2

-字段描述符索引 u2

-字段属性计数器 u2

-字段属性 表

方法计数器 u2

方法 表

-访问标识符 u2

-方法名索引 u2

-方法描述符索引 u2

-方法属性计数器 u2

-方法属性 表

前段编译器

将高级语言源文件编译成Class文件的过程就是前端编译的过程。这是java跨平台执行的关键。

前段编译器只负责将高级语言,编译成字节码,不负责具体的性能优化之类的,这些要在执行引擎中的JIT及时编译器中负责。

前段编译器编译的流程如下。

词法分析

检查关键字,引用等等有没有错误。

语法分析

根据具体的高级语言的语法分析,是否出现语法错误。

语义分析

根据逻辑关系,分析是否有可能出现的逻辑问题。比如未初始化啊,数组越界之类的。这部分的功能有限,只能发现较为明显的逻辑错误。

生成字节码

类加载过程

字节码文件并不存放在内存中,而是在内存外(可能是在磁盘中,也可能在网络中,也可能是动态生成的)。当需要用到的时候再由类加载去寻找并载入内存到运行时数据区才可以被JVM使用。

类加载的过程有7个阶段。

加载——验证——准备——解析——初始化——使用——卸载

加载

1 通过某种方式找到对应的Class文件,获取到二进制数据流。

2 解析二进制数据流,并根据来建立对应方法区中的数据结构。

3 创建java.lang.Class类对象实例,用来作为方法区访问类数据的入口。(也就是给一个索引到方法区数据结构的对象)

Class类的构造方法是私有的,只有JVM可以创建。然后这个Class实例对象,是元空间的入口,也是实现反射的关键数据。通过Class类提供的接口方法,可以获得这个描述类的种种信息。

验证

属于链接中的第一步。

加载到内存之后,我们就可以快速的对字节码进行验证,保证字节码是合法,合规合理的。

字节码格式验证,字节码语义验证,字节码验证(逻辑),符号引用验证。

这和前端编译器,编译流程很像,词法分析对应格式验证,语法分析对应语义验证,语义分析对应字节码验证,以及符号引用验证。

准备阶段

这个时候,字节码通过验证了,那么可以开始完善类的静态部分了,这样一个Class对象算是可用。

也就是静态的成员变量,分配内存,初始化值。

1 分配内存,并进行初始化内存。

对于类的静态成员变量,都会先对内存空间初始化一个值,根据类型不同,初始化的也不同。

java不支持boolean原生类型,内部实现实际上是通过int实现。默认int 0对应false。

这个初始化默认值对于static final修饰的基本数据类型无用,所以没有,因为其不需要多这么个初始为默认值的步骤,而可以直接确定最终值,在准备阶段直接进行显式赋值。(另外对于String类型在显式赋值中不涉及方法或构造器调用,其初始化是在链接阶段的准备环节进行)

对于实例变量,在准备阶段不会进行初始化,因为实例变量会随着实例对象分配到堆中,你不能提前知道要创建实例对象了并提前给创建好。

仅仅是初始化内存空间,相当于清理垃圾,并不会执行任何代码来赋值,这一步是后面初始化阶段做的事情)

解析阶段

将符号引用,转换为实际的直接引用。在实际的运行环境中,寻找符号引用的直接引用,替换到类对象中。

这个阶段有可能在初始化之后再进行,不确定。

初始化阶段

这是类装载的最后一个阶段。这个阶段,JVM才会执行初始化代码,根据类的初始化代码进行初始化类对象。

最重要的工作就是执行<cinit>()方法。(类的初始化方法),这个方法只能由java编译器生成,并只能被JVM调用(我们无法调用)。

<cinit>()是根据类的静态成员赋值语句以及static代码块合并形成的。但是需要记住,在加载子类之前,JVM总会试图先加载其父类,所以父类的《cinit》一定先于子类的《cinit》执行。

如果没有静态赋值语句和静态代码块,那么编译器就不会产生《cinit》方法

cinit()方法只能在类加载的时候被调用一次,后续不能再调用,所以当JVM内部多个线程同时加载一个类的时候就需要保证线程安全,cinit方法自带同步锁,只有一个线程能执行cinit,其他线程都阻塞。等待执行完毕后会通知其他线程返回这个结果。

如果cinit中出现耗时长操作,会导致线程阻塞,难以排查。

初始化时机(cinit调用时机)

因为要执行代码,所以无疑初始化需要时间开销。所以并不是任何时候都能随时进行初始化的。初始化的时机就变得非常重要。

有两种使用类的方式,主动使用会导致初始化发生,被动使用不需要初始化。

1 主动使用(核心就是涉及用到Class对象

也就是当我们需要用到类的静态变量了,那么一定会进行初始化。

  • new实例对象,或者反序列化得到实例对象

  • 反射获取到Class对象

  • 调用静态方法,静态方法可能涉及到静态变量的访问,所以可能静态成员变量需要初始化。

  • 访问未被初始化阶段赋值的静态成员变量(如果访问static final修饰的在准备阶段就赋值的静态变量,那不需要初始化就可以,但是是static final String NAME = new String(”123:“)这种就不可以)

对于接口来说,所有的静态字段都是static final修饰的,效果和类一样。

初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。JVM虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。在初始化一个类时,并不会先初始化它所实现的接口;在初始化一个接口时,并不会先初始化它的父接口。因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态字段时,才会导致该接口的初始化

  • 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类在初始化之前需要实现接口的初始化

  • JVM启动时,用户需要指定一个要执行的主类[包含main()方法的那个类],JVM会先初始化这个主类。这个类在调用main()方法之前被链接和初始化,main()方法的执行将依次加载,链接和初始化后面需要使用到的类。

  • 初次创建MethodHandle实例时,初始化该MethodHandle实例时指向的方法所在的类

2 被动使用

并不是在代码中出现的类,就一定会被加载或者初始化

  • 当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。当通过子类引用父类的静态变量,不会导致子类初始化,而如果访问子类的,就会导致父类进行初始化,以及子类的初始化

  • 通过数组定义类引用,不会触发此类的初始化。直到给具体的数组中的元素赋予对象才会。数组定义的引用,只是从编译阶段确定,所以并不会导致初始化。

  • 引用常量不会触发此类或接口的初始化,因为常量在链接阶段已经被显式赋值

  • 调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。通过反射获取到Class对象,才会导致初始化,例如Class.forName()会导致初始化。

类的卸载

类的卸载,涉及到类加载,类Class对象,类实例之间的引用关系。

类加载器和加载的类对象,相互关联。类加载内部Java集合存放了加载过的类对象引用,而类对象也引用加载他的类加载器。

类实例总是引用代表这个类的Class对象getClass(),类中都有一个静态属性class(通过类名.class可获得),引用着这个类的Class对象。

所以条条大路通Class对象。

所以什么时候卸载类,要等到Class对象不在被引用的时候。

所以类被卸载的三个条件,都是围绕Class对象被引用

  1. 所有类的实例对象都被回收

  2. Class对象没有直接被引用

  3. 类的加载器被GC回收(只有用户自定义加载器能够被回收)

这样图中Order.class实例的三个方向的引用都断了,那么可以卸载类了。

类加载器

实际上属于类加载过程的细节。类加载过程中第一个加载阶段就是类加载器负责的。

类加载器在整个装载阶段,只能影响到类的加载,而无法改变类的链接和初始化行为(存疑)

类加载器必要性

了解类加载器机制,可以解决以下问题。

(1)避免在开发中遇到java.lang.ClassNotFoundException异常或java.lang.NoClassDefFoundError异常时手足无措。

(2)只有了解类加载器的加载机制,才能够在出现异常的时候快速地根据错误异常日志定位并解决问题。

(3)需要支持类的动态加载或需要对编译后的class文件进行加解密操作时,就需要与类加载器打交道。

(4)开发人员可以在程序中编写自定义类加载器来重新定义类的加载规则,以便实现一些自定义的处理逻辑

也就是异常处理,动态或者自定义加载

类加载器的命名空间

每一个类是通过加载它的类加载器加上类本身的名字来确定其在JVM中的唯一性!而不仅仅是通过类的名字。

每个类加载器都有自己的命名空间,命名空间由该类加载器(实例)及所有的父类加载器组成,在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;

这样就保证了类的唯一性。

在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类;在大型应用中,我们往往借助这一特性,来运行同一个类的不同版本。

也就是通过不同的类加载器,加载一个类的不同版本。

类加载的基本特征

通常有三大特征:双亲委派,可见性,单一性

双亲委派实际上就是优先交给上一级加载器加载。这样是为了避免类在其他地方重复加载,第二个是恶意代码不能通过重复加载来替换核心类库。

可见性是下级加载器,可以访问上级加载器加载了哪些类型,反过来是不行的。也就是说高级加载器加载的类,只能看到自己和更高级的类的存在,如果和同级或者下级类进行交互,是ClassNotFound的。而下级加载器加载的类就可以看到上级加载器加载的类的存在。(就像是父类和子类的关系一样,但是只能是访问关系一样)

单一性,因为父加载器加载过的类型对于子加载器是可见的,所以父加载器加载的类型就不会在子加载器中重复加载。但是在同一级的加载器中(兄弟加载器)相互是不可见的,所以同一个类可以被同级别的加载器加载多次

类加载器的分类

实际上就是类加载器分级,是根据什么分级的,不同级别的类加载器有什么职责。

从最本质的来分,JVM有两种类加载器,启动类加载器和自定义类加载器。

自定义类加载器通常是指由开发人员自定义的一类类加载器,但是Java虚拟机规范中规定的更为广泛,凡是从抽象类ClassLoader派生而来的类加载器都是自定义类加载器。那么不是从ClassLoader派生而来的类加载器自然就是启动类加载器了。

无论类加载器的类型如何划分,在程序中我们最常见的类加载器结构如图20-1所示,其中扩展类加载器和应用程序类由抽象类ClassLoader派生而来

实际上不同加载器之间是聚合关系,也就是下级加载器,有上级加载器的引用。而不是继承关系。

只是在ClassLoader这个抽象类中,有一个成员变量引用上一级的加载器,叫做parent。所以上级加载器才被叫做父加载器。

引导类加载器(启动类加载器)

这两种称呼,一种是基于职责负责引导程序运行,一种是指是启动程序的类加载器。

引导类加载器(BootstrapClassLoader,又称启动类加载器)使用C/C++语言实现,嵌套在JVM内部。

引导类加载器不继承java.lang.ClassLoader,没有父类加载器。出于安全考虑,引导类加载器主要用来加载Java的核心库,也就是“JAVA_HOME/jre/lib/rt.jar”或“sun.boot.class.path”路径下的内容,指定为扩展类和应用程序类加载器的父类加载器

所以引导类加载器作用有限,主要用来加载核心类库。

扩展类加载器

扩展类加载器(ExtensionClassLoader)由Java语言编写,间接继承与ClassLoader

扩展类加载器主要负责从java.ext.dirs系统属性所指定的目录或者JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的类放在上述目录下,也会自动由扩展类加载器加载。简言之扩展类加载器主要负责加载Java的扩展库。

应用程序类加载器

也叫做系统加载器,也是由Java语言编写,间接继承于ClassLoader类父类加载器为扩展类加载器。

负责加载环境变量classpath或系统属性java.class.path指定路径下的类库,应用程序中的类加载器默认是应用程序类加载器。(在IDE中,可以查看到JDK的CLASSPATH。在命令行执行的时候CLASSPATH就是当前路径)

它是用户自定义类加载器的默认父类加载器,通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器。

自定义加载器

自定义加载器有很多好处。

  1. 实现插件效果,即插即用。通过启用自定义加载器,加载额外功能。不需要的时候直接回收自定义加载器,就拔除功能。

  2. 隔离加载类。同级类加载器之间相互隔离。所以我们可以通过将不同的类簇通过不同的同级类加载器加载实现隔离。

  3. 修改类加载方式,除了启动类加载器之外,其他的类加载器并非一定引入。所以我们可以改变类加载器的加载。

  4. 扩展加载源。如果需要加载从咔咔郭郭来的类,可以通过自定义加载器载入。

  5. 提高程序的安全性。在一般情况下,使用不同的类加载器去加载不同的功能模块,会提高应用程序的安全性。但是,如果涉及Java类型转换,则加载器反而容易产生不美好的事情。在做Java类型转换时,只有两个类型都是由同一个加载器所加载,才能进行类型转换,否则转换时会发生异常。

获取常见类的加载器

方式加载器类型
class对象.getClassLoader()获取class对象的类加载器
Thread.currentThread().getContextClassLoader()当前线程上下文的类加载器
ClassLoader.getSystemClassLoader()获取系统类加载器
classLoader对象.getParent()获取父类加载器

这些加载器,大多都是由应用类加载器来充当。

可以看到都是应用类加载器加载的。

引导类加载器结果为null,原因是引导类加载器是C++语言编写,并不是一个java对象,所以这里用null展示

数组类特殊

数组类的Class对象,不是由类加载器创建的,而是在Java运行期JVM根据需要自动创建的。数组类的类加载器可以通过Class.getClassLoader()方法返回,如果数组元素是引用数据类型,类加载器与数组当中元素类型相同,如果数组元素类型是基本数据类型,就没有类加载器

 System.out.println(int[][].class.getClassLoader());//输出nullSystem.out.println(ClassLoaderTest[][].class.getClassLoader());//输出sun.misc.Launcher$AppClassLoader@18b4aac2

源码分析

有必要学习类加载器的源码。

ClassLoader主要方法

抽象类ClassLoader的主要方法(内部没有抽象方法)如下。

1)public final ClassLoader getParent()该方法作用是返回该类加载器的父类加载器。

2)public Class<?>loadClass(String name)throws ClassNotFoundException该方法作用是加载名称为name的类,返回结果为java.lang.Class类的实例。如果找不到类,则抛出“ClassNotFoundException”异常。

该方法中的逻辑就是双亲委派模型的实现

具体操作就是:

  1. sychronized保证同步没问题

  2. 查看自己是否加载过 通过findLoadedClass ,也就是protected final Class<?>findLoadedClass(String name) )

  3. 调用父加载器进行加载(如果父为空,调用findBootstrapClassOrNull加载)

  4. 父加载器失败,自己加载 通过findClass(name)

  5. 进行链接操作 resolveClass (也就是 验证,准备,解析 这三个操作)

这里的findClass方法就很重要了,最为加载器的兜底逻辑,负责查找二进制名为name的类,返回的是Class类实例。

protected Class<?>findClass(String name)throws ClassNotFoundException

在jdk1.2之后,官方已经不建议我们重写loadClass方法,因为loadClass方法中实现保证了双亲委派机制的逻辑。我们自己的逻辑建议写在findClass方法中。当loadClass()方法中父类加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模型。

在findClass()中,应该调用defineClass(),将找到的二进制流转换成Class对象。

protected final Class<?>defineClass(String name,byte [] b,int off,int len)

通过这个方法不仅能够通过class文件实例化Class实例对象,也可以通过其他方式实例化Class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象

protected final void resolveClass(Class<?>c)

使用该方法可以使用类的Class对象创建完成的同时也被解析。前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将class文件中的符号引用转换为直接引用

也就是

SecureClassLoader与URLClassLoader

ClassLoader中有很多方法没有实现。SecureClassLoader新增了对Class源的验证,权限之类的方法。

他的子类URLClassLoader给ClassLoader众多没实现的方法提供了实现。例如findClass(),findResouce()方法。新增了通过URLClassPath类来协助获取Class字节码流的功能。

我们可以通过继承自URLClassLoader来避免实现过于复杂的findClass()和字节码获取代码。

ExtClassLoader与AppClassLoader

这两个类加载器都继承自URLClassLoader。是sun.misc.lanucher的内部静态类。sun.misc.lanucher主要用来启动主应用程序。

类加载调用方法

一般通过Class.forName(全限定名),或者classLoader实例.loadClass(全限定名)加载一个类。

不同的是Class方法会在加载到内存同时进行初始化。

而classLoader实例方法,只会加载到内存,不会触发实例化。

让自定义类加载器加载类的办法

如果在findClass中不做什么改变,那么类一般都会让父加载器也就是AppClassLoader给加载了。所以我们需要在loaderClass的时候传入类名,而不是全限定名,这样父加载器就找不到类,会交给自定义加载器加载。在findClass中我们再拼接出类的路径,找到类文件,然后进行加载。

双亲委派模型的改变

双亲委派机制并不是必须的,而是java设计者推荐的一种类加载器实现机制。

他的好处是:

  1. 避免重复加载

  2. 保护程序安全

打破双亲委派机制也无法破坏核心类库的唯一加载

在ClassLoader中的final defineClass方法中,为核心类库提供了一层保护机制。无论是什么类加载器,最终都会调用defineClass这个方法,这个方法final不能重写,在其内部调用preDefineClass方法,对核心类库进行保护。

他的劣势是:

因为我们只设计了父级类加载器的成员变量引用,那么我们的类去寻找其他的类的时候,只能先获取自己的ClassLoader,然后从ClassLoader中获取findLoadedClass(),找不到只能向上找loadedClass。所以无法访问下级类加载器加载的类。

破坏双亲委派机制的三种

为了兼容性

jdk1.2之前,并没有引入双亲委派机制,所以自定义类加载器很多都是通过重写loadClass实现的。在引入之后,为了兼容这些代码,就没有以技术手段防止loadClass被重写。创建了一个prorected findClass来代替重写loadClass。

SPI场景(不同的类加载器加载的类之间的交互)

简单来说就是接口定义在了启动类加载器中,而实现类定义在了其他类加载器中,当启动类加载器需要加载其他子类加载器路径中的类时,需要使用线程上下文类加载器(默认是应用程序类加载器),这样以上下文加载器为中介,使得启动类加载器中的代码也可以访问应用类加载器中的类。

线程上下文类加载器,实际上是一个帽子,主要看我们让哪一个类加载器来带上这个帽子。带上什么帽子起什么作用。原来的findLoadedClass是一条只能向上的线,通过线程上下文类加载器,这样就可以形成一个环。我们通常让APPClassLoader来充当线程上下文类加载器,这样AppClassloader,ExtratClassLoader,BootstrapClassLoader形成了一个环,那么这三个类加载器中加载的类就都相互可查询可见了。

为了热部署

追求程序的动态性,代码热部署,模块热替换等。

java并不天生支持热替换。热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。

如果一个类已经加载到系统中,通过修改类文件,并无法让系统再来加载并重定义这个类。

但是如果我们就是想要重新加载并定义这个类呢?为了不影响程序的运行,我们不能卸载这个类,但是我们可以通过加载一个同名类来替换。要做到这件事,首先我们需要防止触发重复加载,我们需要换一个类加载器,这样JVM会认为是不同的类,然后加载进去,不能调用loadClass这样会交由AppClassLoader加载,就是会认为加载过了。

复习路线

JVM 复习1-CSDN博客

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

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

相关文章

Sentieon软件快速入门指南

Sentieon软件为完整的纯软件基因变异检测二级分析方案&#xff0c;其分析流程完全忠于BWA、GATK、MuTect2、STAR、Minimap2、Fgbio、picard等金标准的数学模型。在匹配开源流程分析结果的前提下&#xff0c;大幅提升WGS、WES、Panel、UMI、ctDNA、RNA等测序数据的分析效率和检出…

数字信号处理:自动增益控制(AGC)

自动增益控制&#xff1a; &#xff1a;自动增益控制&#xff08;Automatic Gain Control, AGC&#xff09;是一种信号处理技术&#xff0c;用于在接收端调整输入信号的增益&#xff08;或放大系数&#xff09;&#xff0c;以保持信号在一个合适的强度范围内&#xff0c;从而防…

RAG中的代表性上下文压缩方案总结:从RECOMP、CompAct到COCOM

今天是2024年11月5日&#xff0c;星期二&#xff0c;北京&#xff0c;天气晴 昨天有说到RAG中的长文本压缩&#xff0c;现有的上下文压缩方法主要分为基于词汇的压缩&#xff08;硬提示&#xff0c;如LLMLingua和RECOMP&#xff09;和基于嵌入的压缩(软提示&#xff0c;如Gist…

创新材料科技:铜冷却壁助力高炉节能降耗

高炉用铜冷却壁是高炉内部的一种构件&#xff0c;通常用于高炉的炉身部分。它的主要功能是在高炉冶炼过程中冷却炉壁&#xff0c;以防止炉壁过热。铜冷却壁通常由铜制成&#xff0c;因为铜具有良好的导热性和耐腐蚀性&#xff0c;能够有效地将热量从高炉内部传导到外部&#xf…

免费送源码:Java+ssm+MySQL ssm小区车辆信息管理系统的设计与实现 计算机毕业设计原创定制

摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作…

云轴科技ZStack在CID大会上分享VF网卡热迁移技术

近日&#xff0c;2024中国云计算基础架构开发者大会&#xff08;以下简称CID大会&#xff09;在北京举行。此次大会集中展示了云计算基础架构技术领域最前沿的科创成果&#xff0c;汇聚众多的技术专家和行业先锋&#xff0c;共同探讨云计算基础设施的最新发展和未来趋势。云轴科…

ES6中数组新增了哪些扩展?

ES6中数组新增了哪些扩展&#xff1f; 1、扩展运算符的应⽤ ES6通过扩展元素符 … &#xff0c;好⽐ rest 参数的逆运算&#xff0c;将⼀个数组转为⽤逗号分隔的参数序列 console.log(...[1, 2, 3]) // 1 2 3 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 [...documen…

「Mac畅玩鸿蒙与硬件15」鸿蒙UI组件篇5 - Slider 和 Progress 组件

Slider 和 Progress 是鸿蒙系统中的常用 UI 组件。Slider 控制数值输入&#xff0c;如音量调节&#xff1b;Progress 显示任务的完成状态&#xff0c;如下载进度。本文通过代码示例展示如何使用这些组件&#xff0c;并涵盖 进度条类型介绍、节流优化、状态同步 和 定时器动态更…

GitHub个人主页美化

效果展示 展示为静态效果&#xff0c;动态效果请查看我的GitHub页面 创建GitHub仓库 创建与GitHub用户名相同的仓库&#xff0c;当仓库名与用户名相同时&#xff0c;此仓库会被视作特殊仓库&#xff0c;其README.md&#xff08;自述文件&#xff09;会展示在GitHub个人主页…

Windows 命令提示符(cmd)中输入 mysql 并收到错误消息“MySQL不是内部或外部命令,也不是可运行的程序或批处理文件?

目录 背景: 过程&#xff1a; 1.找到MySQL安装的路径 2.编辑环境变量 3.打开cmd&#xff0c;输入mysql --version测试成功 总结: 背景: 很早之前安装了Mysql数据库&#xff0c;想查询一下当前安装的MySQL客户端的版本号&#xff0c;我在命令行界面输入mysql --verion命令回…

<项目代码>YOLOv8 夜间车辆识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

太强了!Ollama + MaxKB零代码本地搭建个人知识库AI应用,数据安全,还可以有权限控制!!

零代码本地搭建AI应用 &#x1f4da; 借助开源的&#xff0c;大模型应用不再遥不可及 &#x1f680; 当提到“大模型”和“本地部署”&#xff0c;很多人可能第一反应是&#xff1a;“这是不是只有那些顶尖的技术大牛才能搞定&#xff1f;” 其实&#xff0c;随着开源工具的发…

Unreal5从入门到精通之如何在指定的显示器上运行UE程序

前言 我们有一个设备,是一个带双显示器的机柜,主显示器是一个小竖屏,可以触屏操作,大显示器是一个普通的横屏显示器。我们用这个机柜的原因就是可以摆脱鼠标和键盘,直接使用触屏操作,又可以在大屏观看,非常适合用于教学。 然后我们为这款机柜做了很多个VR项目,包括Uni…

Docker安装XXL-JOB分布式调度任务

一、持久化 1、下载 xxl-job 源码,找到持久化脚本 2、创建 xxl-job 数据库,将上述文件中的脚本在本库执行即可 create database xxl_job charset utf8mb4 collate utf8mb4_general_ci; 二、安装 1、下载 xxl-job 镜像 docker pull xuxueli/xxl-job-admin:2.4.1 2、创建…

【华为HCIP实战课程三十】中间到中间系统协议IS-IS路由渗透及TAG标识详解,网络工程师

一、路由泄露 1、默认情况Level 1不会学到Level2的明细路由&#xff0c;L2可以学到L1的明细路由 2、FIB数据转发&#xff0c;路由负载&#xff0c;通过随机数据中的五元组hash,hash值决定数据走哪条链路 R1设备ping和telnet通过抓包查看走的都是S1/0/0接口 抓包进行过滤;ip.a…

如何将MySQL彻底卸载干净

目录 背景&#xff1a; MySQL的卸载 步骤1&#xff1a;停止MySQL服务 步骤2&#xff1a;软件的卸载 步骤3&#xff1a;残余文件的清理 步骤4&#xff1a;清理注册表 步骤五:删除环境变量配置 总结&#xff1a; 背景&#xff1a; MySQL卸载不彻底往往会导致重新安装失败…

死锁(Dead Lock)

目录 一. 死锁出现的场景 1. 一个线程, 一个锁对象 2. 两个线程, 两个锁对象 3. N个线程, M个锁对象 二. 造成死锁的必要条件 1. 锁是互斥的 2. 锁是不可被抢占的 3.请求和保持 4. 循环等待 三. 死锁的解决方案 1. 预防死锁 2. 死锁产生后的解决 一. 死锁出现的场景…

【Android 系统中使用CallStack类来追踪获取和操作调用栈信息】

Android系统CallStack类的使用 定义使用方法使用场景注意事项应用举例 定义 在 Android 系统中&#xff0c;CallStack 类是一个用于获取和操作调用栈信息的工具类。这个类通常用于调试和日志记录&#xff0c;以帮助开发者了解函数调用的顺序和位置。以下是您提供的代码片段的解…

深度学习基础知识-残差网络ResNet

目录 一、ResNet 的核心思想&#xff1a;残差学习&#xff08;Residual Learning&#xff09; 二、ResNet 的基本原理 三、ResNet 网络结构 1. 残差块&#xff08;Residual Block&#xff09; ResNet 的跳跃连接类型 2. 网络结构图示 四、ResNet 的特点和优势 五、ResNe…

【Mac】安装 VMware Fusion Pro

VMware Fusion Pro 软件已经正式免费提供给个人用户使用&#xff01; 1、下载 【官网】 下拉找到 VMware Fusion Pro Download 登陆账号 如果没有账号&#xff0c;点击右上角 LOGIN &#xff0c;选择 REGISTER 注册信息除了邮箱外可随意填写 登陆时&#xff0c;Username为…