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 / long | 0 / 0L | static int x; → x = 0 |
float / double | 0.0f / 0.0d | static double y; → y = 0.0 |
boolean | false | static boolean flag; → flag = false |
引用类型 (如String ) | null | static 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.核心流程
-
创建自定义加载器:
MyClassLoader classLoader = new MyClassLoader("D:/test");
这个加载器会从D盘的test文件夹找类文件
-
加载类:
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
加载User1类,实际查找路径是:D:/test/com/tuling/jvm/User1.class
-
运行类方法:
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.实际效果
程序会:
- 自定义类加载器,类加载器的路径就是
("D:/test")
- com.tuling.jvm.User1:在
D:/test的路径下创建
com/tuling/jvm,然后把User1.class丢进去 - 自定义加载器就会从d盘加载这个类
- 打印出加载这个类的加载器名称
输出结果示例:
=======自己的加载器加载类调用方法=======
com.tuling.jvm.MyClassLoaderTest$MyClassLoader
自定义类加载器的默认父类类加载器是应用程序类加载器
后续理由反射机制调用方法输出:自己的加载器加载类的调用方法
理解:
- 自定义加载器加载类:
MyClassLoader
从指定路径(D:/test
)加载User1.class
文件 - 反射调用方法:通过反射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类就会输出我们自己的加载器,这就是双亲委派机制!
相关文章:

JVM学习专题(一)类加载器与双亲委派
目录 1、JVM加载运行全过程梳理 2、JVM Hotspot底层 3、war包、jar包如何加载 4、类加载器 我们来查看一下getLauncher: 1.我们先查看getExtClassLoader() 2、再来看看getAppClassLoader(extcl) 5、双亲委派机制 1.职责明确,路径隔离ÿ…...

PyTorch API 9 - masked, nested, 稀疏, 存储
文章目录 torch.randomtorch.masked简介动机什么是 MaskedTensor? 支持的运算符一元运算符二元运算符归约操作查看与选择函数 torch.nested简介构造方法数据布局与形状支持的操作查看嵌套张量的组成元素填充张量的相互转换形状操作注意力机制 与 torch.compile 的配…...

进程相关面试题20道
一、基础概念与原理 1.进程的定义及其与程序的本质区别是什么? 答案:进程是操作系统分配资源的基本单位,是程序在数据集合上的一次动态执行过程。核心区别: 动态性:程序是静态文件,进程是动态执行实例…...
微信小程序学习之轮播图swiper
轮播图是小程序的重要组件,我们还是好好学滴。 1、上代码,直接布局一个轮播图组件(index.wxml): <swiper class"swiper" indicator-active-color"#fa2c19" indicator-color"#fff" duration"{{durati…...
【万字逐行详解】深入解析ONNX Runtime图像分类程序main函数
本文将全面、详尽地解析一个使用ONNX Runtime进行图像分类的C++程序,不省略任何一行代码,逐行解释其语法和实现原理。这个程序展示了现代C++在计算机视觉领域的完整应用流程,从模型加载到结果可视化,涵盖了异常处理、性能分析等工程实践。 程序完整解析 1. 主函数框架 i…...

Linux复习笔记(五) 网络服务配置(dhcp)
二、网络服务配置 2.5 dhcp服务配置(不涉及实际操作) 要求:知道原理和常见的参数配置就行 2.5.1 概述DHCP(Dynamic Host Configuration Protocol,动态主机配置协议) DHCP(Dynamic Host Conf…...
智慧工厂管理平台推荐?智慧工厂解决方案提供商有哪些?智慧工厂管理系统哪家好?
随着工业4.0和“双碳”目标的推进,智慧工厂管理平台成为制造企业数字化转型的核心工具。本文基于技术实力、应用场景、安全可靠三大维度,结合最新行业实践与用户需求,精选出十大智慧工厂解决方案提供商,助您快速匹配行业需求&…...
鸿蒙OSUniApp 实现的语音输入与语音识别功能#三方框架 #Uniapp
UniApp 实现的语音输入与语音识别功能 最近在开发跨平台应用时,客户要求添加语音输入功能以提升用户体验。经过一番调研和实践,我成功在UniApp项目中实现了语音输入与识别功能,现将过程和方法分享出来,希望对有类似需求的开发者有…...

windows版redis的使用
redis下载 Releases microsoftarchive/redishttps://github.com/microsoftarchive/redis/releases redis的启动和停止 进入路径的cmd 启动:redis-server.exe redis.windows.conf 停止:ctrlc 连接redis 指定要连接的IP和端口号 -h IP地址 -p 端口…...

Java版OA管理系统源码 手机版OA系统源码
Java版OA管理系统源码 手机版OA系统源码 一:OA系统的主要优势 1. 提升效率 减少纸质流程和重复性工作,自动化处理常规事务,缩短响应时间。 2. 降低成本 节省纸张、打印、通讯及人力成本,优化资源分配。 3. 规范管理 固化企…...

NineData 社区版 V4.1.0 正式发布,新增 4 条迁移链路,本地化数据管理能力再升级
NineData 社区版 V4.1.0 正式更新发布。本次通过新增 4 条迁移链路扩展、国产数据库深度适配、敏感数据保护增强等升级,进一步巩固了其作为高效、安全、易用的数据管理工具的定位。无论是开发测试、数据迁移,还是多环境的数据管理,NineData…...

进阶2_1:QT5多线程与定时器共生死
1、在widget.ui中使用 LCD Number控件 注意:若 LCD 控件不是多线程,LCD控件则会瞬间自增到最大的数值,如上图,说明两者都是多线程处理 2、实现方式 1、创建 LCD 控件并修改为 LCD1 2、创建任务类 mytask. h,对任务类…...

在虚拟机Ubuntu18.04中安装NS2教程及应用
NS2简介 一、主要组成部分: 1.NS2:模拟器本身,负责执行TCL脚本进行模拟,并生成trace文件输出结果。 2.NAM:网络动画模拟器,用于将模拟结果可视化。 二、使用的语言: 1.C:NS2中最重要…...

VBA —— 第6章子程序与函数
子程序:实现特定功能的程序代码块 子程序语法: [修饰符] Sub 子程序名称([参数1,参数2,参数3]) 代码块 End Sub 子程序如何调用: 1 . 子程序名 [参数1,参数2,...] 2. Call 子程序名 [(参…...
MySQL知识点总结(持续更新)
聚合函数通常用于对数据进行统计和聚合操作。以下是一些常见数据库系统(如 MySQL、PostgreSQL、Oracle、SQL Server 等)中常用的聚合函数: 常见的数据库聚合函数: COUNT():计算指定列中非空值的数量 SELECT COUNT(*) …...

全新开发-iVX图形化编程VS完整IDE
本文针对传统软件开发的效率与可控性矛盾,系统阐释 iVX"图形化编程 全栈 IDE" 的复合架构如何突破行业瓶颈。通过 "可视化建模 - 标准代码生成 - 独立运行" 的技术闭环,iVX 实现开发效率提升 60% 与源码完全可控的双重目标。研究揭…...
【android bluetooth 协议分析 12】【A2DP详解 1】【车机侧蓝牙音乐免切源介绍】
“车机蓝牙音乐免切源” 是近年来车载系统(IVI,In-Vehicle Infotainment)中常见的一个用户体验优化功能。它主要是为了简化蓝牙音乐播放流程、减少用户操作,提升使用便捷性。 一、什么是“切源”? 在车机系统中&#…...

【Linux系列】跨平台安装与配置 Vim 文本编辑器
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
蓝桥杯题库经典题型
1、数列排序(数组 排序) 问题描述 给定一个长度为n的数列,将这个数列按从小到大的顺序排列。1<n<200 输入格式 第一行为一个整数n。 第二行包含n个整数,为待排序的数,每个整数的绝对值小于10000。 输出格式 输出…...
Spring Boot整合MyBatis全攻略:原理剖析与最佳实践
MyBatis作为Java生态中最流行的ORM框架之一,与Spring Boot的结合极大地简化了数据库访问层的开发。本文将深入剖析Spring Boot整合MyBatis的核心机制,详细介绍各种使用方式,并分享实际开发中的高级技巧和最佳实践。 一、Spring Boot与MyBati…...

十天学会嵌入式技术之51单片机—day-10
第 20 章 18B20 温度检测 20.1 18B20 概述 20.1.1 简介 18B20 是一种常用的数字温度传感器,广泛应用于环境监测、工业控制、家居自动化 和设备温度监控等领域。 20.1.2 引脚功能 18B20 引脚功能如下图所示,需要特别强调的是,18B20 采用 1-…...

【C++】17. 多态
上一章节中我们讲了C三大特性的继承,这一章节我们接着来讲另一个特性——多态 1. 多态的概念 多态(polymorphism)的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态),这里我们重点讲运行时多态…...
Excel的详细使用指南
### **一、Excel基础操作** #### **1. 界面与基本概念** - **工作簿(Workbook)**:一个Excel文件(扩展名.xlsx)。 - **工作表(Worksheet)**:工作簿中的单个表格(默认名…...
没经过我同意,flink window就把数据存到state里的了?
欢迎关注我 不知道大家在初次使用Flink的时候,是否对Flink中定义本地变量和状态比较好奇,这俩有啥区别? 而且在使用Window API时明明没有显式地创建状态,也没调用getState(),却依然把每个窗口里的所有元素都自动缓存…...
Python+OpenCV打造AR/VR基础框架:从原理到实战的全链路解析
引言:重新定义数字与现实的边界 在元宇宙概念持续升温的当下,AR(增强现实)与VR(虚拟现实)技术正成为连接物理世界与数字世界的桥梁。Python凭借其丰富的计算机视觉生态(尤其是OpenCV库…...

家用或办公 Windows 电脑玩人工智能开源项目配备核显的必要性(含 NPU 及显卡类型补充)
一、GPU 与显卡的概念澄清 首先需要明确一个容易误解的概念:GPU 不等同于显卡。 显卡和GPU是两个不同的概念。 【概念区分】 在讨论图形计算领域时,需首先澄清一个常见误区:GPU(图形处理单元)与显卡(视…...

实现一个简单的 TCP 客户端/服务器
注意: TCP 三次握手建立连接建立连接后,TCP 提供全双工的通信服务,也就是在同一个连接中,通信双方 可以在同一时刻同时写数据,相对的概念叫做半双工,同一个连接的同一时刻,只能由一方来写数据T…...

对抗帕金森:在疾病阴影下,如何重掌生活主动权?
帕金森病,一种影响全球超 1000 万人的神经退行性疾病,正无声地改变着患者的生活轨迹。随着大脑中多巴胺分泌减少,患者逐渐出现肢体震颤、肌肉僵硬、步态迟缓等症状,甚至连扣纽扣、端水杯这类日常动作都变得艰难。更棘手的是&#…...

鸿蒙 UIAbility组件与UI的数据同步和窗口关闭
使用 EventHub 进行数据通信 Stage模型概念图 根据 Stage 模型概念图 UIAbility 先于 ArkUI Page 创建 所以,事件要先 .on 订阅 再 emit 发布 假如现在有页面 Page1 和他的 UIAbility // src/main/ets/page1ability/Page1Ability.ets onCreate(want: Want, laun…...
DeepSeek 赋能汽车全生态:从产品到服务的智能化跃迁
目录 一、引言二、DeepSeek 助力汽车产品介绍与推广2.1 新车性能参数与技术亮点宣传文案2.2 汽车品牌故事与文化内涵挖掘2.3 汽车广告创意与宣传方案设计 三、DeepSeek 赋能汽车售后服务支持3.1 汽车维修保养知识科普文章创作3.2 常见故障诊断与解决方案生成3.3 汽车用户个性化…...