JVM学习专题(一)类加载器与双亲委派

目录

1、JVM加载运行全过程梳理

2、JVM Hotspot底层

3、war包、jar包如何加载

4、类加载器

我们来查看一下getLauncher:

1.我们先查看getExtClassLoader()

2、再来看看getAppClassLoader(extcl)

5、双亲委派机制

1.职责明确,路径隔离​:​

2.那为什么要这么设计呢?我们再次来看看源码:

3.那为什么非得从应用程序加载器开始呢?

4.双亲委派机制源码剖析:

实现双亲委派机制:

5.为什么要实现双亲委派机制

6、全盘负责委托机制

7、自定义类加载器

1.核心流程

2.关键点

3.实际效果


1、JVM加载运行全过程梳理

当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到 JVM

代码执行流程图:

C++的启动程序通过 JNI 启动了一个Java虚拟机,并且JVM 内部用 C++ 实现的引导类加载器先加载核心类 ,然后 Java 层的 sun.misc.Launcher 被初始化,创建扩展类加载器(ExtClassLoader)和应用类加载器(AppClassLoader),最终通过 loadClass() 按双亲委派机制加载磁盘上的字节码文件。​最后再调用Main方法

2、JVM Hotspot底层

HotSpot 主要集中在 JVM 初始化、类加载机制和字节码执行

其中loadClass的类加载过程有如下几步:

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

加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的 main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口验证:校验字节码文件的正确性准备:给类的静态变量分配内存,并赋予默认值

验证:验证格式是否正确,比如开头的cafe babe

准备:静态变量做一个初始化赋值(final关键字变成常量不再是变量)

静态变量类型​​准备阶段赋的默认值​​示例​
int / long0 / 0Lstatic int x; → x = 0
float / double0.0f / 0.0dstatic double y; → y = 0.0
booleanfalsestatic boolean flag; → flag = false
引用类型(如Stringnullstatic String s; → s = null
final static常量​直接赋代码中的值​final static int z = 100; → z = 100

解析:符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(符号到内存地址的转换)(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用

初始化:对类的静态变量初始化为指定的值,执行静态代码块

jar包的Terminal打开可以输入指令查看代码信息(类、常量池...) 

javap -v xxx.class

3、war包、jar包如何加载

类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。

类加载器的引用:这个类到类加载器实例的引用对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

注意,主类在运行过程中如果使用到其它类,会逐步加载这些类。 jar包或war包里的类不是一次性全部加载的,是使用到时才加载(懒加载)。

4、类加载器

上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器

  • 引导类加载器​Bootstrap:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等
  • 扩展类加载器​​ExtClassLoader​​ :负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包
  • 应用程序类加载器AppClassLoader:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
  • 自定义加载器:负责加载用户自定义路径下的类包
public class TestJDKClassLoader {public static void main(String[] args) {// 1. 打印核心类、扩展类、应用类的加载器System.out.println(String.class.getClassLoader());  // null (Bootstrap)System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());  // ExtClassLoaderSystem.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());  // AppClassLoader// 2. 获取并打印类加载器层次ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();ClassLoader extClassloader = appClassLoader.getParent();ClassLoader bootstrapLoader = extClassloader.getParent();  // nullSystem.out.println("the bootstrapLoader : " + bootstrapLoader);System.out.println("the extClassloader : " + extClassloader);System.out.println("the appClassLoader : " + appClassLoader);// 3. 打印各加载器加载的路径System.out.println("\nbootstrapLoader加载以下文件:");URL[] urls = Launcher.getBootstrapClassPath().getURLs();for (URL url : urls) {System.out.println(url);}System.out.println("\nextClassloader加载以下文件:");System.out.println(System.getProperty("java.ext.dirs"));System.out.println("\nappClassLoader加载以下文件:");System.out.println(System.getProperty("java.class.path"));}
}

运行结果:

 35 null36 sun.misc.Launcher$ExtClassLoader37 sun.misc.Launcher$AppClassLoader3839 the bootstrapLoader : null40 the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d41 the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc4243 bootstrapLoader加载以下文件:
44 file:/D:/dev/Java/jdk1.8.0_45/jre/lib/resources.jar45 file:/D:/dev/Java/jdk1.8.0_45/jre/lib/rt.jar46 file:/D:/dev/Java/jdk1.8.0_45/jre/lib/sunrsasign.jar47 file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jsse.jar48 file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jce.jar49 file:/D:/dev/Java/jdk1.8.0_45/jre/lib/charsets.jar50 file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jfr.jar51 file:/D:/dev/Java/jdk1.8.0_45/jre/classes
5253 extClassloader加载以下文件:
54 D:\dev\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\ext5556 appClassLoader加载以下文件:
57 D:\dev\Java\jdk1.8.0_45\jre\lib\charsets.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\deploy.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\access‐bridge‐64.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;D:ev\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\javaws.ar;D:\dev\Java\jdk1.8.0_45\jre\lib\jce.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfr.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jsse.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\management
agent.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\plugin.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\resources.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\rt.jar;D:\ideaProjects\project‐all\target\classes;C:\Users\zhuge\.m2\repository\org\apache\zookeeper\zookeeper\3.4.12\zookeeper‐3.4.12.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j
api\1.7.25\slf4j‐api‐1.7.25.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j‐log4j12\1.7.25\slf4j‐log4j12
1.7.25.jar;C:\Users\zhuge\.m2\repository\log4j\log4j\1.2.17\log4j
1.2.17.jar;C:\Users\zhuge\.m2\repository\jline\jline\0.9.94\jline
0.9.94.jar;C:\Users\zhuge\.m2\repository\org\apache\yetus\audience
annotations\0.5.0\audience‐annotations‐0.5.0.jar;C:\Users\zhuge\.m2\repository\io\netty\netty\3.10.6.Final\netty‐3.10.6.Final.jar;C:\Users\zhuge\.m2\repository\com\google\guava\guava\22.0\guava‐22.0.jar;C:\Users\zhuge\.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305‐1.3.9.jar;C:\Users\zhuge\.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations‐2.0.18.jar;C:\Users\zhuge\.m2\repository\com\google\j2objc\j2objc‐annotations\1.1\j2objc‐annotations‐1.1.jar;C:\Users\zhuge\.m2\repository\org\codehaus\mojo\animal
sniffer‐annotations\1.14\animal‐sniffer‐annotations‐1.14.jar;D:\dev\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar

我们来查看一下getLauncher:

我们先进入到Launcher.class,直接再idea搜索就行了

这时候你会发现他返回了一个launcher,我们追进去查看launcher怎么定义的

你会发现他早就初始化好了在加载阶段的时候,是一个单例。接下来我们查看一下launcher的构造方法:

1.我们先查看getExtClassLoader()

你会发现extcl = ExtClassLoader.getExtClassLoader();获取到了扩展类加载器,接下来我们去查看扩展类加载器是怎么初始化的:

他这里创建了一个实例,返回了一个实例,我们继续追源码

然后我们发现他在这返回了一个初始化的类加载器,他在初始化的时候还会调用他的父类URLClassLoader.java,这个类可以通过传过来的磁盘文件路径通过一些文件的读写加载到内存里面去

2、再来看看getAppClassLoader(extcl)

loader = AppClassLoader.getAppClassLoader(extcl);

这里的extcl是extcl = ExtClassLoader.getExtClassLoader();

 我们追入getAppClassLoader(extcl)查看:

其中final String s = System.getProperty("java.class.path");拿到我们的环境变量

最后他又返回了一个应用程序加载器return new AppClassLoader(urls, extcl);

同样他也会调用URLClassLoader 

那extcl = ExtClassLoader.getExtClassLoader()到底去哪了呢?当我们不断的追传入的第二个参数

最终我们追到了ClassLoader

找到了这个定义:private final ClassLoader parent;

所以AppClassLoader的parent是ExtClassLoader,这里不是父类加载器的关系,父类加载器是URLClassLoader(static class AppClassLoader extends URLClassLoader),而ExtClassLoader呢

他是空的,因为ExtClassLoader算是引导类加载器,引导类加载器是C++写的

5、双亲委派机制

JVM类加载器是有亲子层级结构的,如下图

这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再 委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的 类加载路径中查找并载入目标类。

比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类(lib里面)加载路径里找了半天 没找到Math类,则向下退回加载Math类的请求扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Math类又向下退回Math类的加载请求给应用程序类加载器, 应用程序类加载器于是在自己的类加载路径(在 java.class.path(用户类路径)中查找 .class 文件或 JAR 包)里找Math类,结果找到了就自己加载了。。 双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载

1.职责明确,路径隔离​:

  • ​Bootstrap​​ 只加载 JRE/lib 下的核心类(如 java.lang.*)。
  • ​ExtClassLoader​​ 只加载 JRE/lib/ext 下的扩展类。
  • ​AppClassLoader​​ 负责所有​​用户类路径(java.class.path)​​的类,包括:
    • 项目代码(如 com.example.MyClass)。
    • 第三方依赖(如 Maven/Gradle 引入的 JAR 包)。

​只要类在用户类路径中存在,AppClassLoader 一定能加载​​,因为父加载器不会越权加载这些类。

2.那为什么要这么设计呢?我们再次来看看源码:

当我去得到类加载器的时候:

C++语言在最终加载类的时候就会调用这个方法,获得这个loader,从而加载应用程序的类(比如Math),这个laoder在初始化Launcher的时候就

 最终你可以发现还是先加载的AppClassLoader​​应用类加载器

3.那为什么非得从应用程序加载器开始呢?

实际上,对于一个web程序来说,95%以上都是这个应用程序类加载器去加载,只有第一次加载的时候需要过这个流程:应用程序类加载器==>拓展类加载器==>引导类加载器==>拓展类加载器==>应用程序类加载器,后续再次去运行的时候,类已经加载到应用程序类加载器了,直接拿来用就行了,如果是从引导类加载器开始,那每次都要走到应用程序类加载器才行。 

4.双亲委派机制源码剖析:

我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader 的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:

1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。

2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加 载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加 载。

3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载 

实现双亲委派机制:

launcher下的loadClass类:

追到父类 :classLoader下的loadClass类:

重点来了,建议背下来!

1.会调用Class<?> c = findLoadedClass(name)方法来检查是不是已经加载过了,加载过了就肯定不是0,就直接return c,追入findLoadedClass()方法你会发现调用的本地方法findLoadedClass0(),就是c++代码:private native final Class<?> findLoadedClass0(String name);

2.当c是0也就是第一次加载的时候,会判断还有没有父类然后继续判断有没有加载过,但此时在c = parent.loadClass(name, false)之后,已经是ExtClassLoader​了,同样c是0,进入第一个if语句,然后ExtClassLoader​的parent是null!!,所以调findBootstrapClassOrNull这个方法,就是引导类加载器,底层也是C++,第一次加载肯定是null,之后就会进入c = findClass(name)这个方法,这个extClassLoader没有findClass方法但是他的父类URLClassLoader有findClass方法,后续大部分都是本地方法,查看不了,但是第一次加载,返回的c肯定还是null,重点来了!!此时return c之后的出口是c = parent.loadClass(name, false),也就是第一个if之后,之后就回到了AppClassLoader,又会调用findclass()方法,也要调用父类URLClassLoader的findClass方法最终拿到目标类

5.为什么要实现双亲委派机制

  • 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改
  • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性

​实例代码:

 package java.lang;23 public class String {4 public static void main(String[] args) {5 System.out.println("**************My String Class**************");6 }7 }89运行结果:
10错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:11 public static void main(String[] args)12否则 JavaFX 应用程序类必须扩展javafx.application.Application

解释:

由于双亲委派机制,​​Bootstrap 永远优先加载 JDK 核心类​​,用户自定义的同名类会被忽略:当这个String类从应用程序类加载器到拓展类加载器都没找到,就回去引导类加载器找,结果找到了在JDK的在rt.jar包下,然后加载到jvm里面运行直接加载 JDK 的原生类,​​不会加载用户自定义的 String 类​,没有main()方法。

6、全盘负责委托机制

“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入

7、自定义类加载器

自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空 方法,所以我们自定义类加载器主要是重写findClass 方法

public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();return data;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);// defineClass将一个字节数组转为Class对象// 这个字节数组是class文件读取后最终的字节数组return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}}public static void main(String args[]) throws Exception {// 初始化自定义类加载器// 会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("D:/test");// D盘创建 test/com/tuling/jvm 几级目录// 将User类的复制类User1.class丢入该目录Class clazz = classLoader.loadClass("com.tuling.jvm.User1");// 1. 通过反射创建实例Object obj = clazz.newInstance();  // 相当于 new User1()// 2. 通过反射获取方法Method method = clazz.getDeclaredMethod("sout", null);  // 获取无参的sout方法// 3. 通过反射调用方法method.invoke(obj, null);  // 相当于 obj.sout()System.out.println(clazz.getClassLoader().getClass().getName());}
}/*
运行结果:
=======自己的加载器加载类调用方法=======
com.tuling.jvm.MyClassLoaderTest$MyClassLoader
*/

第一步:继承ClassLoader

第二步:重写findClass方法

1.核心流程

  1. ​创建自定义加载器​​:

    MyClassLoader classLoader = new MyClassLoader("D:/test");

    这个加载器会从D盘的test文件夹找类文件

  2. ​加载类​​:Class clazz = classLoader.loadClass("com.tuling.jvm.User1");

    加载User1类,实际查找路径是:D:/test/com/tuling/jvm/User1.class

  3. ​运行类方法​​:

    Object obj = clazz.newInstance(); // 创建对象 Method method = clazz.getDeclaredMethod("sout", null); // 获取sout方法 method.invoke(obj, null); // 调用方法

2.关键点

  • loadByte()类:从磁盘上把类文件读到一个字节数组里面
  • findClass():最终把这个字节数字读入到defineClass(name, data, 0, data.length)方法里面去,name是类名,data ​​磁盘上 .class 文件的二进制原始数据
  • 最终输出证明类确实是由我们的自定义加载器加载的

3.实际效果

程序会:

  1. 自定义类加载器,类加载器的路径就是("D:/test")
  2. com.tuling.jvm.User1:在D:/test的路径下创建com/tuling/jvm,然后把User1.class丢进去
  3. 自定义加载器就会从d盘加载这个类
  4. 打印出加载这个类的加载器名称

输出结果示例:

=======自己的加载器加载类调用方法=======
com.tuling.jvm.MyClassLoaderTest$MyClassLoader

自定义类加载器的默认父类类加载器是应用程序类加载器 

后续理由反射机制调用方法输出:自己的加载器加载类的调用方法

理解:

  1. ​自定义加载器加载类​​:MyClassLoader从指定路径(D:/test)加载User1.class文件
  2. ​反射调用方法​​:通过反射API调用加载类中的sout()方法
// 1. 通过反射创建实例
Object obj = clazz.newInstance();  // 相当于 new User1()// 2. 通过反射获取方法
Method method = clazz.getDeclaredMethod("sout", null);  // 获取无参的sout方法// 3. 通过反射调用方法
method.invoke(obj, null);  // 相当于 obj.sout()

这时候同学们可能忘记反射机制了,没关系我带大家用一个例子来复习一遍

1.准备一个简单的类

public class User {private String name;private int age;public User() {this.name = "默认用户";this.age = 18;}public User(String name, int age) {this.name = name;this.age = age;}public void printInfo() {System.out.println("用户信息: " + name + ", " + age + "岁");}// getter和setter省略...
}

2.普通方法调用

public class NormalExample {public static void main(String[] args) {// 1. 直接使用new创建对象User user1 = new User();User user2 = new User("张三", 25);// 2. 直接调用方法user1.printInfo();  // 输出: 用户信息: 默认用户, 18岁user2.printInfo();  // 输出: 用户信息: 张三, 25岁// 3. 直接访问public字段(如果有的话)// user1.name = "李四";  // 如果name是public的// 4. 编译时就能发现错误// User user3 = new User("参数错误");  // 编译报错,没有匹配的构造方法}
}

 3.反射调用

import java.lang.reflect.*;public class ReflectionExample {public static void main(String[] args) throws Exception {// 1. 获取Class对象Class<?> userClass = Class.forName("User");// 2. 创建对象(无参构造)Object user1 = userClass.newInstance();// 3. 创建对象(带参构造)Constructor<?> constructor = userClass.getConstructor(String.class, int.class);Object user2 = constructor.newInstance("张三", 25);// 4. 调用方法Method printMethod = userClass.getMethod("printInfo");printMethod.invoke(user1);  // 输出: 用户信息: 默认用户, 18岁printMethod.invoke(user2);  // 输出: 用户信息: 张三, 25岁// 5. 访问私有字段Field nameField = userClass.getDeclaredField("name");nameField.setAccessible(true);  // 突破private限制nameField.set(user1, "反射修改的名字");printMethod.invoke(user1);  // 输出: 用户信息: 反射修改的名字, 18岁// 6. 运行时才会发现错误try {Constructor<?> wrongConstructor = userClass.getConstructor(String.class);Object user3 = wrongConstructor.newInstance("参数错误");} catch (NoSuchMethodException e) {System.out.println("运行时才发现构造方法不存在");}}
}

言归正传,接下来我们来探讨最后输出的那句话 :System.out.println(clazz.getClassLoader().getClass().getName());

为什么这句话输出的加载器是AppClassLoader

答案是因为AppClassLoader中也有这个类,当我们删除AppClassLoader下的User1类就会输出我们自己的加载器,这就是双亲委派机制!

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

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

相关文章

部署安装gitlab-ce-17.9.7-ce.0.el8.x86_64.rpm

目录 ​编辑 实验环境 所需软件 实验开始 安装部署gitlab171.配置清华源仓库&#xff08;版本高的系统无需做&#xff09;vim /etc/yum.repos.d/gitlab-ce.repo 2.提前下载包dnf localinstall gitlab-ce-17.9.7-ce.0.el8.x86_64.rpm --rocklinux 3.修改配…

使用LoRA微调Qwen2.5-VL-7B-Instruct完成电气主接线图识别

使用LoRA微调Qwen2.5-VL-7B-Instruct完成电气主接线图识别 动机 任务适配需求 Qwen2.5-VL在视觉理解方面表现优异&#xff0c;但电气主接线图识别需要特定领域的结构化输出能力&#xff08;如设备参数提取、拓扑关系解析&#xff09;。微调可增强模型对专业符号&#xff08;如…

系统集成项目管理工程师学习笔记

第九章 项目管理概论 1、项目基本要素 项目基础 项目是为创造独特的产品、服务或成果而进行的临时性工作。 项目具有临时性、独特性、渐进明细的特点。项目的“临时性”是指项目只有明确的起点和终点。“临时性”并一定意味着项目的持续时间短。 项目可宣告结束的情况&…

Secs/Gem第七讲(基于secs4net项目的ChatGpt介绍)

好的&#xff0c;那我们现在进入&#xff1a; 第七讲&#xff1a;掉电重连后&#xff0c;为什么设备不再上报事件&#xff1f;——持久化与自动恢复的系统设计 关键词&#xff1a;掉电恢复、状态重建、初始化流程、SecsMessage 缓存机制、自动重连、事件再注册 本讲目标 你将理…

室内定位:热门研究方向与未解难题深度解析

I. 引言:对普适性室内定位的持续探索 A. 室内定位在现代应用中的重要性 室内定位系统(IPS)正迅速成为众多应用领域的基石技术,其重要性源于现代社会人们约70%至90%的时间在室内度过的事实 1。这些应用横跨多个行业,包括应急响应 1、智能建筑与智慧城市 6、医疗健康(如病…

Android学习总结之Glide自定义三级缓存(实战篇)

一、为什么需要三级缓存 内存缓存&#xff08;Memory Cache&#xff09; 内存缓存旨在快速显示刚浏览过的图片&#xff0c;例如在滑动列表时来回切换的图片。在 Glide 中&#xff0c;内存缓存使用 LruCache 算法&#xff08;最近最少使用&#xff09;&#xff0c;能自动清理长…

Linux的文件查找与压缩

查找文件 find命令 # 命令&#xff1a;find 路径范围 选项1 选项1的值 \[选项2 选项2 的值…]# 作用&#xff1a;用于查找文档&#xff08;其选项有55 个之多&#xff09;# 选项&#xff1a;# -name&#xff1a;按照文档名称进行搜索&#xff08;支持模糊搜索&#xff0c;\* &…

python处理异常,JSON

异常处理 #异常处理 # 在连接MySQL数据库的过程中&#xff0c;如果不能有效地处理异常&#xff0c;则异常信息过于复杂&#xff0c;对用户不友好&#xff0c;暴露过多的敏感信息 # 所以&#xff0c;在真实的生产环境中&#xff0c; 程序必须有效地处理和控制异常&#xff0c;按…

线程的两种实现方式

线程的两种实现方式——内核支持线程&#xff08;kernal Supported Thread, KST&#xff09;&#xff0c; 用户级线程&#xff08;User Level Thread, ULT&#xff09; 1. 内核支持线程 顾名思义&#xff0c;内核支持线程即为在内核支持下的那些线程&#xff0c;它们的创建&am…

vue3基础学习(上) [简单标签] (vscode)

目录 1. Vue简介 2. 创建Vue应用 2.1 下载JS文件 2.2 引用JS文件 2.3 调用Vue方法​编辑 2.4 运行一下试试: 2.5 代码如下 3.模块化开发模式 3.1 Live Server插件 3.2 运行 4. 常用的标签 4.1 reactive 4.1.1 运行结果 4.1.2 代码: 4.2 ref 4.2.1 运行结果 4.2.2…

自定义分区器-基础

什么是分区 在 Spark 里&#xff0c;弹性分布式数据集&#xff08;RDD&#xff09;是核心的数据抽象&#xff0c;它是不可变的、可分区的、里面的元素并行计算的集合。 在 Spark 中&#xff0c;分区是指将数据集按照一定的规则划分成多个较小的子集&#xff0c;每个子集可以独立…

深入解析HTTP协议演进:从1.0到3.0的全面对比

HTTP协议作为互联网的基础协议&#xff0c;经历了多个版本的迭代演进。本文将详细解析HTTP 1.0、HTTP 1.1、HTTP/2和HTTP/3的核心特性与区别&#xff0c;帮助开发者深入理解网络协议的发展脉络。 一、HTTP 1.0&#xff1a;互联网的奠基者 核心特点&#xff1a; 短连接模式&am…

基于windows环境Oracle主备切换之后OGG同步进程恢复

基于windows环境Oracle主备切换之后OGG同步进程恢复 场景&#xff1a;db1是主库&#xff0c;db2是备库&#xff0c;ogg从db2备库抽取数据同步到目标数据库 db1 - db2(ADG) – ogg – targetdb 场景&#xff1a;db2是主库&#xff0c;db1是备库&#xff0c;ogg从db1备库抽取数…

微服务,服务粒度多少合适

项目服务化好处 复用性&#xff0c;消除代码拷贝专注性&#xff0c;防止复杂性扩散解耦合&#xff0c;消除公共库耦合高质量&#xff0c;SQL稳定性有保障易扩展&#xff0c;消除数据库解耦合高效率&#xff0c;调用方研发效率提升 微服务拆分实现策略 统一服务层一个子业务一…

【工奥阀门科技有限公司】签约智橙PLM

近日&#xff0c;工奥阀门科技有限公司正式签约了智橙泵阀行业版PLM。 忠于质量&#xff0c;臻于服务&#xff0c;精于研发 工奥阀门科技有限公司&#xff08;以下简称工奥阀门&#xff09;坐落于浙江永嘉&#xff0c;是一家集设计、开发、生产、销售、安装、服务为一体的阀门…

2025-5-15Vue3快速上手

1、setup和选项式API之间的关系 (1)vue2中的data,methods可以与vue3的setup共存 &#xff08;2&#xff09;vue2中的data可以用this读取setup中的数据&#xff0c;但是反过来不行&#xff0c;因为setup中的this是undefined &#xff08;3&#xff09;不建议vue2和vue3的语法混用…

基于智能推荐的就业平台的设计与实现(招聘系统)(SpringBoot Thymeleaf)+文档

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

什么是路由器环回接口?

路由器环回接口&#xff08;LoopbackInterface&#xff09;是网络设备中的一种逻辑虚拟接口&#xff0c;不依赖物理硬件&#xff0c;但在网络配置和管理中具有重要作用。以下是其核心要点&#xff1a; 一、基本特性 1.虚拟性与稳定性 环回接口是纯软件实现的逻辑接口&#x…

HOT100 (滑动窗口子串普通数组矩阵)

先填坑 滑动窗口 3. 无重复字符的最长子串 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。 思路:用一个uset容器存放当前滑动窗口中的元素 #include <bits/stdc++.h> using namespace std; class Solution {public:int lengthOfLongestSubstring(st…

工作实战之关于数据库表的备份

文章目录 1. dbeaver导出相关表到本地2. 使用sql语句3. 导入数据 1. dbeaver导出相关表到本地 常规情况下&#xff0c;如果想备份数据库的某张表&#xff0c;特别是临时备份或者表中数据不多的情况下&#xff0c;直接将数据库表中导出即可&#xff0c;后续可根据导出的insert语…