JVM ②-双亲委派模型 || 垃圾回收GC
这里是Themberfue
- 在上节课对内存区域划分以及类加载的过程有了简单的了解后,我们再了解其他两个较为重要的机制,这些都是面试中常考的知识点,有必要的话建议背出来,当然不是死记硬背,而是要有理解的背~~~
- 如果对 JVM 的其他机制感兴趣的话,建议去阅读一些书籍,我的建议是失眠的时候看看,可以有效改善睡眠(bushi
类加载器
- 类加载器从 JDK 1.0 就出现了,最初只是为了满足 Java Applet(已经被淘汰) 的需要。后来,慢慢成为 Java 程序中的一个重要组成部分,赋予了 Java 类可以被动态加载到 JVM 中并执行的能力。
- 我们先看官方 API 文档的介绍:A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system. Every Class object contains a reference to the ClassLoader that defined it. Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.
- 简单来说:
- 在 Java 中,类加载器(ClassLoader)是负责加载类文件(.class 文件)的组件。类加载器的主要作用是将字节码加载到 JVM 中,并使其成为JVM 可识别并执行的类。
- 类加载器是Java 运行时环境的一部分,它控制了类的加载过程,并且是 JVM 的重要组成部分之一。类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
- 每个类都是通过 类加载器加载的。类加载器将字节码文件加载到内存,并将其转化为 JVM 内部的 Class 对象,从而使得程序能够使用该类。
- 每个 Java 类都有一个引用指向加载它的
ClassLoader
。- 数组类不是通过
ClassLoader
创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。class Class<T> {...private final ClassLoader classLoader;@CallerSensitivepublic ClassLoader getClassLoader() {//...}... }
- 在前边多线程的学习中,我们学习了懒汉模式,Java 类加载的规则也是采用懒汉模式来加载类的。
- JVM 启动时,并不会一次性加载所有类,而是根据需要动态加载类。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。这便是懒汉模式一个应用~~~
- 对于已经加载的类会被放在
ClassLoader
中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。public abstract class ClassLoader {...private final ClassLoader parent;// 由这个类加载器加载的类。private final Vector<Class<?>> classes = new Vector<>();// 由VM调用,用此类加载器记录每个已加载类。void addClass(Class<?> c) {classes.addElement(c);}... }
分类
Bootstrap ClassLoader(引导类加载器)
- 作用:负责加载 JDK 核心类库,也就是 Java标准库(如
java.lang.*
、java.util.*
等)。- 加载路径:它从 JRE 的
lib
目录下加载类,如rt.jar
、resources.jar
等。- 特点:Bootstrap 类加载器是 JVM 自带的,用 C++ 实现,不能被 Java 代码继承。它加载的类在 JVM 启动时就被加载到内存。
Extension ClassLoader(扩展类加载器)
- 作用:负责加载 JDK 扩展类库,即 JRE/lib/ext/ 目录下的类,或者
java.ext.dirs
指定的路径中的类。- 加载路径:默认加载路径是
JRE/lib/ext/
目录下的 JAR 文件和类文件。- 特点:
Extension ClassLoader
是由ClassLoader
继承而来,用 Java 实现。Application ClassLoader(应用类加载器)
- 作用:负责加载 应用程序的类库,即
classpath
下的类。- 加载路径:从
classpath
环境变量指定的路径加载类文件(例如:.class
文件或.jar
包)。- 特点:是最常见的类加载器,它负责加载我们自己编写的 Java 类文件,通常由
java.lang.ClassLoader
类提供。Java 的第三方库,例如使用 Maven 管理的库自定义类加载器(Custom ClassLoader)
- 作用:开发者可以继承
ClassLoader
类,重新实现findClass()
方法,来创建自己特定的类加载器。- 用途:自定义类加载器通常用于 插件化、动态加载、热部署 等场景。
类加载器的父子关系
- Java 中的类加载器是按照父子层级结构组织的。每个类加载器都有一个父类加载器,类加载的过程遵循 父加载器优先加载的原则。
- 类加载器的父子关系如下:
- Bootstrap ClassLoader 是根加载器,它不继承任何类加载器。
- Extension ClassLoader 是 Bootstrap ClassLoader 的子类。
- Application ClassLoader 是 Extension ClassLoader 的子类。
双亲委派模型
- Java 的类加载机制采用了 双亲委派模型。该模型的核心思想是:每个类加载器都会首先委托给父类加载器进行加载,只有父类加载器无法加载时,子类加载器才会尝试加载。
- 流程:当一个类加载器需要加载一个类时,它会 首先委托给父加载器。如果父加载器不能加载该类(即类不存在),子加载器才会 自己尝试加载该类。如果子加载器依然不能加载该类,则 返回错误。
- 简单来说就是:在进行类加载时,也就是通过全限定类名找 .class 文件时,会先从 Application ClassLoader 作为入口开始,随后把 加载类 这样的任务委托给其父类,也就是 Extension ClassLoader 来进行。Extension ClassLoader 也不会立刻开始 加载类 的任务,而是继续委托给其父类,也就是 Bootstrap ClassLoader 来进行。Bootstrap Loader 作为顶层类加载器,自然没有父类了,便会从 Bootstrap Loader 这一层开始查找,也就是先从 Java标准库 开始查找。如果 Bootstrap Loader 没有找到,则交给其子类 Extension ClassLoader 查找,若 Extension ClassLoader 没有找到,则交给其子类 Application ClassLoader 查找,若依旧没有找到且没有子类时,则抛出异常。
- ⚠️双亲委派模型并不是一种强制性的约束,只是 JDK 官方推荐的一种方式。
- 其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个
MotherClassLoader
和一个FatherClassLoader
。个人觉得翻译成单亲委派模型更好一些,不过,国内既然翻译成了双亲委派模型并流传了,按照这个来也没问题,不要被误解了就好。- 双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API(如
java.lang.String
)不被篡改。
类加载器的应用
自定义类加载器
- 在一些特殊的应用场景下,如 热部署、插件机制、动态代理 等,可以通过 自定义类加载器 来控制类的加载方式。例如,JSP 热部署、Spring 容器的 Bean 加载等,都需要自定义类加载器。
Java 的反射机制
- 反射机制(如
Class.forName()
)通过类加载器动态加载类,并获取类的构造方法、字段、方法等信息,支持 运行时动态创建对象、调用方法 等操作。隔离加载
- Java 类加载器的父子层次结构使得不同应用程序可以 隔离类的加载,避免不同程序之间的类冲突。例如,Tomcat、Jetty 等应用服务器支持 Web 应用程序的隔离,它们为每个应用创建不同的类加载器,防止不同应用之间共享类。
垃圾回收(GC)
- 在过去 C语言时,我们在申请了一块内存空间后,要去手动释放这块内存,也就是调用 free() 函数,若不释放掉,很容易造成内存泄漏和内存溢出等问题。就算代码块里确实调用了 free() 函数,有时候程序也可能因为 bug 没有走到那里,从而导致内存泄漏等。
- 手动释放内存太麻烦了,所以 Java 为了开发的便捷性,便引入 GC(Garbage Collection,垃圾回收) 机制,JVM 会自动识别出哪块内存是不需要使用的,并释放掉这些内存,不再需要我们过度关注。
- 相比较于 C++,Java 的代码执行时间要短一些,就是因为 JVM 的这些机制。不过,随着 JDK 版本的不断迭代,目前 Java 的执行时间以及和 C++ 较为接近了,STW 时间大部分情况下都在 1ms 以下。(STW,Stop The World,在 JVM GC 时,就会因为需要GC,其他业务的代码不得不停止运行,必须等待 GC 执行完后才能继续运行,这个时间间隔就被称为 STW)
- Java 的 GC 主要是针对对象内存的回收和对象内存的分配。所以,Java GC 最核心的功能是 堆 内存中对象的分配与回收。堆 是 GC 主要管理的区域,所以 堆 也被称作 GC 堆(Garbage Collected Heap)。在 Java 代码执行的过程中,必然伴随着新对象的创建和旧对象的消亡,所以,回收内存 的本质就是 回收对象。
- 在 Java 中,对象的生命周期通常由 JVM 管理,当对象不再被引用时,它就变成了 "垃圾",这些对象占用了内存资源。没有垃圾回收机制,程序员需要手动清理这些不再使用的对象,这可能导致:内存泄漏:程序员没有正确地释放不再使用的对象,导致内存不能被回收。内存溢出:程序分配的内存资源不能及时释放,导致系统内存不足,程序崩溃。
GC 的工作过程大致可以分为两步:
- 找到 垃圾,即为不再使用的对象(死亡对象)
- 释放 垃圾,该对象所申请的内存释放掉
死亡对象的判断
引用计数法
在 new 对象时,给对象单独添加一个引用计数器:
- 每当有一个地方引用它,计数器就加 1;
- 当引用失效(置为 null ),计数器就减 1;
- 任何时候计数器为 0 的对象就是不可能再被使用的(即为死亡对象)。
这便是 Python 与 PHP 使用的方法,这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,是因为其存在某些致命的缺点。
- 内存消耗:需要为每个对象都额外开辟一块空间添加计数器,如果一个对象的大小为8字节,计数器的空间通常为4字节,这就比原来多了 50% 的内存空间。
- 对象之间循环引用:所谓对象之间的相互引用问题,如下面代码所示:除了对象
objA
和objB
相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。public class Test {Object instance = null;public static void main(String[] args) {Test objA = new Test();Test objB = new Test();objA.instance = objB;objB.instance = objA;objA = null;objB = null;} }
可达性分析
引用计数法在空间上的开销较大,而可达性分析则是用时间换空间的思想。
- 以代码中一些特定的对象,作为 GCRoots,也就是遍历的起点,从 GCRoots 开始向下搜索,节点所走过的路径称为引用链,所经过的对象被标记为 “可达”,当一个对象到 GC Roots 没有任何引用链相连的话,该对象则被标记为 “不可达”,则证明此对象是不可用的,需要被回收。
- 下图为一些对象的引用关系,其中,Obj5 - Obj7 之间虽然有引用关系,但是从 GCRoots开始并不能遍历到 Obj5 - Obj7,被认为不可达,然后回收。
- 可达性分析 会尽可能的遍历每一个对象,遍历完之后将那些 “不可达” 的对象进行释放。
- 但是,即使在可达性分析法中不可达的对象,也并非是 “非死不可” 的,这时候它们暂时处于 “缓刑阶段”,要真正宣告一个对象的死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行
finalize
方法。当对象没有覆盖finalize
方法,或finalize
方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。Object
类中的finalize
方法一直被认为是一个糟糕的设计,成为了 Java 语言的负担,影响了 Java 语言的安全和 GC 的性能。JDK9 版本及后续版本中各个类中的finalize
方法会被逐渐弃用移除。忘掉它的存在吧~~~❓哪些对象可以作为 GCRoots
- 虚拟机栈(栈帧中的局部变量表)中引用的对象
- 本地方法栈(Native 方法)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
- JNI(Java Native Interface)引用的对象
可达性分析便是 Java 采取的方案,这种方案的弊端也很明显,在每次可达性分析时,都会尽可能的遍历所有对象,如果对象过多,其所消耗的时间也会相应的增长,STW 的时间就会变大。
垃圾收集算法
标记-清除算法
找到可以清除的垃圾对象后,我们还需要对其进行释放。标记-清除算法(Mark and Sweep)是最基础的垃圾回收算法。后续的算法都是根据这个算法的缺点进行优化得来的。
- 它分为两个阶段:
- 标记阶段:遍历所有对象,标记出哪些对象是可以访问的(即有引用指向的对象)。
- 清除阶段:回收所有没有标记的对象,释放它们占用的内存。
这个算法的缺点也十分明显。首先体现在效率上,标记和清除这两个过程的效率都不高。再者,从空间上看,标记清除后会产生大量不连续的内存碎片。
- 我们申请内存时,要求这块内存必须是连续的,不能是分散的。
- 假设你需要申请 1GB 的内存空间,目前空闲内存空间为 4GB,但依然申请失败,这是因为 4GB 的内存大部分都是零零散散分布的,并非连续的。
复制算法
复制算法(Copying)将内存分为两部分:一部分用来存储活动对象,另一部分为空闲区。每次垃圾回收时,将存活的对象复制到另一部分内存上,从而回收掉已经不再使用的内存。
- 该算法虽然解决了标记-清除算法带来的内存碎片问题。
- 但需要额外的内存空间,通常是原内存空间的两倍大小,这对于内存的空间利用率是非常低的。
- 如果存活的对象过多,复制所消耗的时间和资源也是巨大的。
标记-整理算法
标记-整理算法(Mark-Compact)是标记-清除算法的改进版,标记阶段仍然和标记-清除算法相同,但清除阶段不再是直接删除不可达对象,而是将存活对象整理到内存的一端,最后释放出内存的连续空间。
- 虽然解决了内存碎片的问题以及空间利用率低的问题。
- 但是相较于标记-清除算法,标记-整理算法的实现更加复杂。
- 且由于多了整理这一步,因此效率也不高,适合这种垃圾回收频率不是很高的场景。
分代收集算法
分代收集算法(Generational Garbage Collection)基于这样一个假设:大部分对象都很快会变成垃圾。因此将堆内存划分为多个区域(如:新生代(伊甸区、幸存区)、老年代),通过将新生代的对象频繁回收来提高回收效率。
- 堆内存通常被分为以下三个部分:
- 新生代内存(Young Generation)
- 老生代(Old Generation)
- 永久代(Permanent Generation)
- 下图所示的 Eden(伊甸) 区、两个 Survivor(幸存) 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。JDK 8 版本之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是直接内存,用于存储类元信息、静态变量等。
分代垃圾回收集具体过程
- 分代收集算法根据不同 “代” 次区分对象的大小,每进行 GC 一次,每个对象的 “代” 次就会 +1。大多数对象最开始都是 0 代,也就是放在 Eden 区。⚠️每次 Minor GC 后,代数才会 +1。
新生代垃圾回收(Minor GC)
- 当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC(这是一个特殊的 GC)。
- Minor GC 只扫描新生代(而不会扫描整个堆)。
- 由于大部分对象都存活不到 Minor GC 发生,所以此时所剩的对象也不多,此时 Minor GC 会回收 Eden 中的垃圾对象,存活对象会被复制到 Survivor 区(S0 或 S1)。因为存活对象不多,只需要付出少量对象的复制成本就可以完成每次垃圾收集,所以使用复制算法并不会产生过大的开销。
- 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象代设为 1(Eden 区 -> Survivor 区后对象的初始代数变为 1)。
- 对于 Survivor 区的对象也会进行 GC 回收,伴随着每次 Minor GC,Survivor 区的对象的 “代” 次也会相应的增加,Survivor 区的对象会在 S0 和 S1 区反复横跳。多次 GC 仍存活的对象,达到一定阈值(默认为 15 代)后,晋升到 老年代。⚠️如果 Survivor 区满,存活对象直接晋升到 老年代。
老年代垃圾回收(Major GC / Full GC)
- 当老年代内存不足时(如 Survivor 区晋升过来的对象太多)、显式调用
System.gc()
时、Minor GC 失败(即新生代对象无法晋升到老年代,而老年代已满)时会触发 Major GC。- 由于老年代的对象可能较少且生命周期都很长,所以使用标记-清除算法标记存活对象并清除不可达对象,且使用标记-整理算法整理存活对象,避免内存碎片。
Full GC
- Full GC 代表的是 全堆垃圾回收,即 同时回收新生代和老年代。
- 当老年代满了(如 Minor GC 失败后,需要腾出老年代空间)、元空间(Metaspace)满了、调用 System.gc() 时会触发 Full GC。
- 由于 Full GC 影响较大,因为它会暂停所有线程,通常需要尽量避免。
- Survivor 区溢出:如果 Survivor 区装不下存活对象,则直接晋升老年代。
- 大对象直接进入老年代:大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。会直接分配到老年代,避免在新生代频繁移动。
- 大对象直接进入老年代的行为是由虚拟机动态决定的,它与具体使用的垃圾回收器和相关参数有关。大对象直接进入老年代是一种优化策略,旨在避免将大对象放入新生代,从而减少新生代的垃圾回收频率和成本。
JVM的垃圾回收器
Java 提供了多种垃圾回收器,常见的有:
Serial GC
- 特点:单线程工作,所有的垃圾回收工作都由一个线程完成。
- 适用场景:适合内存较小、单核 CPU 的环境。
Parallel GC(并行回收)
- 特点:多个线程并行进行垃圾回收,适合多核 CPU 的环境。
- 适用场景:适合 CPU 核心数多的环境,能显著提高 GC 的吞吐量。
CMS(Concurrent Mark-Sweep)
- 特点:旨在最小化垃圾回收停顿时间。它通过并行标记阶段和并发清除阶段来减少 GC 停顿时间。
- 适用场景:适合对响应时间有严格要求的应用,如 Web 应用。
G1 GC(Garbage-First GC)
- 特点:G1 是一种新型的垃圾回收器,它通过将堆划分为多个区域进行回收,以最小化停顿时间,并保持较高的吞吐量。G1 GC 可以动态调节回收的停顿时间,具有较好的可预测性。
- 适用场景:适用于大内存和对延迟有较高要求的系统。
- JVM 机制的介绍到此就结束了,之后更新什么内容之后再说吧~~~
- 毕竟不知后事如何,且听下回分解
- ❤️❤️❤️❤️❤️❤️❤️
- ⚠️⚠️⚠️本文章部分文案参考——JavaGuide
相关文章:

JVM ②-双亲委派模型 || 垃圾回收GC
这里是Themberfue 在上节课对内存区域划分以及类加载的过程有了简单的了解后,我们再了解其他两个较为重要的机制,这些都是面试中常考的知识点,有必要的话建议背出来,当然不是死记硬背,而是要有理解的背~~~如果对 JVM …...
jQuery介绍(快速、简洁JavaScript库,诞生于2006年,主要目标是简化HTML文档操作、事件处理、动画和Ajax交互)
文章目录 **核心功能 & 亮点**1. **简化 DOM 操作**2. **链式调用**3. **跨浏览器兼容**4. **便捷的事件绑定**5. **Ajax 封装**6. **动画效果** **现状与适用场景**- **传统项目维护**:许多旧系统(如 WordPress 插件、老企业网站)仍依赖…...

python旅游推荐系统+爬虫+可视化(协同过滤算法)
✅️基于用户的协同过滤算法 ✅️有后台管理 ✅️2w多数据集 这个旅游数据分析推荐系统采用了Python语言、Django框架、MySQL数据库、requests库进行网络爬虫开发、机器学习中的协同过滤算法、ECharts数据可视化技术,以实现从网站抓取旅游数据、个性化推荐和直观展…...

Ubuntu 22.04.5 LTS 安装企业微信,(2025-02-17安装可行)
一、依赖包(Ubuntu 20.04/Debian 11) 点击下载https://www.spark-app.store/download_dependencies_latest 1、 下载最新的依赖包。 请访问星火应用商店依赖包下载页面, 下载最新的依赖包。2、解压依赖包 </...
【Excel笔记_6】条件格式和自定义格式设置表中数值超过100保留1位,超过1000保留0位,低于100为默认
方法一:自定义格式 选中需要设置格式的单元格区域。右键选择设置单元格格式,或者在工具栏中选择开始 -> 数字 -> 自定义格式。在类型框中输入以下自定义格式: [>1000]0;[>100]0.0;G/通用格式解释: [>1000]0&…...

UDP与TCP
用UDP一定比用TCP快吗? 假设我们需要在a电脑的进程发一段数据到b电脑的进程我们可以选择使用TCP或UDP协议进行通信。 对于TCP这样的可靠性协议每次消息发出后都能明确知道对方有没有收到,就像打电话一样,只要“喂喂"两下对方就能回你个…...

Web开发技术概述
Web开发技术涵盖了前端和后端开发,以及数据库技术。前端开发包括使用HTML、CSS、JavaScript等原生技术,以及jQuery、Bootstrap、AngularJS、React、Vue等框架。后端开发则涉及ASP.NET、PHP、Python Web(Flask、Django)、Java Web&…...

解压rar格式的软件有哪些?8种方法(Win/Mac/手机/网页端)
RAR 文件是一种常见的压缩文件格式,由尤金・罗谢尔(Eugene Roshal)开发,因其扩展名 “rar” 而得名。它通过特定算法将一个或多个文件、文件夹进行压缩,大幅减小存储空间,方便数据传输与备份。然而…...

uniapp开发:首次进入 App 弹出隐私协议窗口
前言:在移动应用开发中,隐私协议弹窗是一个非常重要的功能。它不仅符合法律法规的要求(如 GDPR、CCPA 等),还能增强用户对 App 的信任感。本文将介绍如何在 Uniapp 开发的 App 中实现“首次进入弹出隐私协议窗口&#…...

执行pnpm run dev报错:node:events:491 throw er; // Unhandled ‘error‘ event的解决方案
vite搭建的vue项目,使用pnpm包管理工具,执行pnpm run dev,报如下错误: 报错原因: pnpm依赖安装不完整,缺少esbuild.exe文件,导致无法执行启动命令。 解决方案: 根据错误提示中提到…...
OpenCV机器学习(4)k-近邻算法(k-Nearest Neighbors, KNN)cv::ml::KNearest类
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::ml::KNearest 是 OpenCV 机器学习模块中的一部分,它提供了实现 k-近邻算法(k-Nearest Neighbors, KNN)的…...

JVM中的线程池详解:原理→实践
一、为什么需要线程池? 在多线程编程中,频繁地创建和销毁线程会带来显著的性能开销。 想象一下,如果你经营一家西餐厅,每次有顾客到来你都雇佣新的服务员,顾客吃完结账后就解雇——这种模式是不是非常效率低下且成本高…...

SNARKs 和 UTXO链的未来
1. 引言 SNARKs 经常被视为“解决”扩容问题的灵丹妙药。虽然 SNARKs 可以提供令人难以置信的好处,但也需要承认其局限性——SNARKs 无法解决区块链目前面临的现有带宽限制。 本文旨在通过对 SNARKs 对比特币能做什么和不能做什么进行(相对)…...
JavaScript设计模式 -- 外观模式
在实际开发中,往往会遇到多个子系统协同工作时,直接操作各个子系统不仅接口繁琐,还容易导致客户端与内部实现紧密耦合。**外观模式(Facade Pattern)**通过为多个子系统提供一个统一的高层接口,将复杂性隐藏…...

百达翡丽(Patek Philippe):瑞士制表的巅峰之作(中英双语)
百达翡丽(Patek Philippe):瑞士制表的巅峰之作 在钟表界,百达翡丽(Patek Philippe) 一直被誉为“世界三大名表”之一,并且常被认为是其中的至高存在。一句“没人能真正拥有一枚百达翡丽&#x…...

阿里云一键部署DeepSeek-V3、DeepSeek-R1模型
目录 支持的模型列表 模型部署 模型调用 WebUI使用 在线调试 API调用 关于成本 FAQ 点击部署后服务长时间等待 服务部署成功后,调用API返回404 请求太长导致EAS网关超时 部署完成后,如何在EAS的在线调试页面调试 模型部署之后没有“联网搜索…...

分享一款AI绘画图片展示和分享的小程序
🎨奇绘图册 【开源】一款帮AI绘画爱好者维护绘图作品的小程序 查看Demo 反馈 github 文章目录 前言一、奇绘图册是什么?二、项目全景三、预览体验3.1 截图示例3.2 在线体验 四、功能介绍4.1 小程序4.2 服务端 五、安装部署5.1 快速开始~~5.2 手动部…...
【练习】【双指针】力扣热题100 283. 移动零
题目 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0] 输出…...

QT 互斥锁
一、概述 1、在多线程编程中,为了防止多个线程同时访问共享资源而导致的不确定性和错误,经常会使用互斥锁(Mutex)进行保护。 2、QMutex是Qt提供的一个互斥锁类,用于确保在同一时间只有一个线程访问共享资源。 3、QM…...
什么是算法的空间复杂度和时间复杂度,分别怎么衡量。
1. 时间复杂度 时间复杂度衡量的是算法运行时间与输入规模之间的关系。它通常用大O记号(Big O Notation)表示,例如 O(1)、O(n)、O(n2) 等。 衡量方法: 常数时间复杂度 O(1):无论输入规模如何,算法的执行时…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...

Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
41道Django高频题整理(附答案背诵版)
解释一下 Django 和 Tornado 的关系? Django和Tornado都是Python的web框架,但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架,鼓励快速开发和干净、实用的设计。它遵循MVC设计,并强调代码复用。Django有…...

【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战
🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…...

【threejs】每天一个小案例讲解:创建基本的3D场景
代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone,无需安装依赖,直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 核心三要素 场景(Scene) 使用 THREE.Scene(…...