JVM類加載器詳解

文章目錄

  • 1.類與類加載器
  • 2.類加載器加載規則
  • 3.JVM 中內置的三個重要類加載器
    • 為什么 獲取到 ClassLoader 為null就是 BootstrapClassLoader 加載的呢?
  • 4.自定義類加載器
    • 什么時候需要自定義類加載器
    • 代碼示例
  • 5.雙親委派模式
    • 類與類加載器
    • 雙親委派模型
    • 雙親委派模型的執行流程
    • 雙親委派模型的好處
    • 打破雙親委派模型方法
  • 6.線程上下文類加載器


1.類與類加載器

類加載器雖然只用于實現類的加載動作,但它在Java程序中起到的作用卻遠超類加載階段。
對于任意一個類,都必須由加載它的類加載器和這個類本身一起共同確立其在Java虛擬機中的唯一性,一個類加載器,都擁有一個獨立的類名稱空間。

這句話可以表達得更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個Class文件,被同一個Java虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。

這里所指的“相等”,包括代表類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括了使用instanceof關鍵字做對象所屬關系判定等各種情況。

官方 API 文檔的介紹:
類加載器是一個負責加載類的對象。ClassLoader 是一個抽象類。給定類的二進制名稱,類加載器應嘗試定位或生成構成類定義的數據。典型的策略是將名稱轉換為文件名,然后從文件系統中讀取該名稱的“類文件”。

每個 Java 類都有一個引用指向加載它的 ClassLoader。不過,數組類不是通過 ClassLoader 創建的,而是 JVM 在需要的時候自動創建的,數組類通過getClassLoader()方法獲取 ClassLoader 的時候和該數組的元素類型的 ClassLoader 是一致的。

從上面的介紹可以看出:

  • 類加載器是一個負責加載類的對象,用于實現類加載過程中的加載這一步。
  • 每個 Java 類都有一個引用指向加載它的 ClassLoader。
  • 數組類不是通過 ClassLoader 創建的(數組類沒有對應的二進制字節流),是由 JVM 直接生成的。

簡單來說,類加載器的主要作用就是動態加載 Java 類的字節碼( .class 文件)到 JVM 中(在內存中生成一個代表該類的 Class 對象)。 字節碼可以是 Java 源程序(.java文件)經過 javac 編譯得來,也可以是通過工具動態生成或者通過網絡下載得來。

其實除了加載類之外,類加載器還可以加載 Java 應用所需的資源如文本、圖像、配置文件、視頻等等文件資源。

2.類加載器加載規則

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);}...
}

3.JVM 中內置的三個重要類加載器

JVM 中內置了三個重要的 ClassLoader:

  1. BootstrapClassLoader(啟動類加載器)最頂層的加載類,由 C++實現,通常表示為 null,并且沒有父級,主要用來加載 JDK 內部的核心類庫( %JAVA_HOME%/lib目錄下的 rt.jar、resources.jar、charsets.jar等 jar 包和類)以及被 -Xbootclasspath參數指定的路徑下的所有類。
  2. ExtensionClassLoader(擴展類加載器):主要負責加載 %JRE_HOME%/lib/ext 目錄下的 jar 包和類以及被 java.ext.dirs 系統變量所指定的路徑下的所有類。
  3. AppClassLoader(應用程序類加載器):面向我們用戶的加載器,負責加載當前應用 classpath 下的所有 jar 包和類。

🌈 拓展一下:
rt.jar:rt 代表“RunTime”,rt.jar是 Java 基礎類庫,包含 Java doc 里面看到的所有的類的類文件。也就是說,我們常用內置庫 java.xxx.*都在里面,比如java.util.*、java.io.*、java.nio.*、java.lang.*、java.sql.*、java.math.*。
Java 9 引入了模塊系統,并且略微更改了上述的類加載器。擴展類加載器被改名為平臺類加載器(platform class loader)。Java SE 中除了少數幾個關鍵模塊,比如說 java.base 是由啟動類加載器加載之外,其他的模塊均由平臺類加載器所加載。

除了這三種類加載器之外,用戶還可以加入自定義的類加載器來進行拓展,以滿足自己的特殊需求。就比如說,我們可以對 Java 類的字節碼( .class 文件)進行加密,加載時再利用自定義的類加載器對其解密。

除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的類加載器都是在 JVM 外部實現的,并且全都繼承自 ClassLoader抽象類。這樣做的好處是用戶可以自定義類加載器,以便讓應用程序自己決定如何去獲取所需的類。

每個 ClassLoader 可以通過getParent()獲取其父 ClassLoader,如果獲取到 ClassLoader 為null的話,那么該類是通過 BootstrapClassLoader 加載的。

public abstract class ClassLoader {...// 父加載器private final ClassLoader parent;@CallerSensitivepublic final ClassLoader getParent() {//...}...
}

為什么 獲取到 ClassLoader 為null就是 BootstrapClassLoader 加載的呢?

這是因為BootstrapClassLoader 由 C++ 實現,由于這個 C++ 實現的類加載器在 Java 中是沒有與之對應的類的,所以拿到的結果是 null。

4.自定義類加載器

除了 BootstrapClassLoader 其他類加載器均由 Java 實現且全部繼承自java.lang.ClassLoader。如果我們要自定義自己的類加載器,很明顯需要繼承 ClassLoader抽象類。

ClassLoader 類有兩個關鍵的方法:

  • protected Class loadClass(String name, boolean resolve):加載指定二進制名稱的類,實現了雙親委派機制 。name 為類的二進制名稱,resolve 如果為 true,在加載時調用 resolveClass(Class<?> c) 方法解析該類。
  • protected Class findClass(String name):根據類的二進制名稱來查找類,默認實現是空方法。

注意:如果我們不想打破雙親委派模型,就重寫 ClassLoader 類中的 findClass() 方法即可,無法被父類加載器加載的類最終會通過這個方法被加載。但是,如果想打破雙親委派模型則需要重寫 loadClass() 方法

什么時候需要自定義類加載器

  • 想加載非 classpath 隨意路徑中的類文件。
  • 都是通過接口來使用實現、希望解耦時,常用在框架設計。
  • 這些類希望予以隔離,不同應用的同名類都可以加載,不會發生沖突,常見于 tomcat 容器。

步驟:

  1. 繼承 ClassLoader 父類
  2. 要遵從雙親委派機制,重寫 findClass 方法
    注意不是重寫 loadClass 方法,否則不會走雙親委派機制
  3. 讀取類文件的字節碼
  4. 調用父類的 defineClass 方法來加載類
  5. 使用者調用該類加載器的 loadClass 方法

代碼示例

public class F {//通過是否運行靜態代碼塊觀察是否被加載并初始化static {System.out.println("bootstrap F init");}
}

自定義類加載器

class MyClassLoader extends ClassLoader {@Override // name 就是類名稱protected Class<?> findClass(String name) throws ClassNotFoundException {//F.class位置String path = "D:\\java\\jvm\\out\\production\\jvm\\" + name + ".class";try {ByteArrayOutputStream os = new ByteArrayOutputStream();Files.copy(Paths.get(path), os);// 得到字節數組byte[] bytes = os.toByteArray();// byte[] -> *.classreturn defineClass(name, bytes, 0, bytes.length);} catch (IOException e) {e.printStackTrace();throw new ClassNotFoundException("類文件未找到", e);}}@Overrideprotected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {// 打印加載類的類加載器System.out.println("Loading class " + name + " with " + this);return super.loadClass(name, resolve);}
}

使用自定義類加載器加載F類

public class Load {public static void main(String[] args) throws Exception {MyClassLoader classLoader = new MyClassLoader();Class<?> c1 = classLoader.loadClass("F");Class<?> c2 = classLoader.loadClass("F");System.out.println(c1 == c2);MyClassLoader classLoader2 = new MyClassLoader();Class<?> c3 = classLoader2.loadClass("F");System.out.println(c1 == c3);System.out.println("c1: " + c1.getClassLoader());System.out.println("c2: " + c2.getClassLoader());System.out.println("c3: " + c3.getClassLoader());c1.newInstance();}
}

輸出

Loading class F with cn.itcast.jvm.t3.load.MyClassLoader@12bb4df8
Loading class F with cn.itcast.jvm.t3.load.MyClassLoader@12bb4df8
true
Loading class F with cn.itcast.jvm.t3.load.MyClassLoader@4cc77c2e
true
c1: sun.misc.Launcher$AppClassLoader@18b4aac2
c2: sun.misc.Launcher$AppClassLoader@18b4aac2
c3: sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrap F init

5.雙親委派模式

類與類加載器

站在Java虛擬機的角度來看,只存在兩種不同的類加載器:

  • 一種是啟動類加載器(BootstrapClassLoader),這個類加載器使用C++語言實現[1],是虛擬機自身的一部分;
  • 另外一種就是其他所有的類加載器,這些類加載器都由Java語言實現,獨立存在于虛擬機外部,并且全都繼承自抽象類java.lang.ClassLoader。

站在Java開發人員的角度來看,類加載器就應當劃分得更細致一些:
JDK 9之前的Java應用都是由啟動、擴展、應用程序類加載器互相配合來完成加載的,如果用戶認為有必要,還可以加入自定義的類加載器來進行拓展,典型的如增加除了磁盤位置之外的Class文件來源,或者通過類加載器實現類的隔離、重載等功能。

類加載器有很多種,當我們想要加載一個類的時候,具體是哪個類加載器加載呢?這就需要提到雙親委派模型了。

  • ClassLoader 類使用委托模型來搜索類和資源。
  • 雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應有自己的父類加載器。
  • ClassLoader 實例會在試圖親自查找類或資源之前,將搜索類或資源的任務委托給其父類加載器。

雙親委派模型

下圖展示的各種類加載器之間的層次關系被稱為類加載器的“雙親委派模型(Parents Delegation Model)”。
在這里插入圖片描述
雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應有自己的父類加載器。不過這里類加載器之間的父子關系一般不是以繼承(Inheritance)的關系來實現的,而是通常使用組合(Composition)關系來復用父加載器的代碼。

注意??:類加載器的雙親委派模型在JDK 1.2時期被引入,并被廣泛應用于此后幾乎所有的Java程序中,但它并不是一個具有強制性約束力的模型,而是Java設計者們推薦給開發者的一種類加載器實現的最佳實踐。

在面向對象編程中,有一條非常經典的設計原則:組合優于繼承,多用組合少用繼承

雙親委派模型的執行流程

java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded// 1. 檢查該類是否已經加載Class<?> c = findLoadedClass(name);if (c == null) {//如果 c 為 null,則說明該類沒有被加載過long t0 = System.nanoTime();try {if (parent != null) {// 2. 有上級的話,委派上級 loadClass來加載該類c = parent.loadClass(name, false);} else {// 3. 如果沒有上級了(ExtClassLoader),則委派BootstrapClassLoaderc = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 4. 每一層找不到,調用 findClass 方法(每個類加載器自己擴展)來加載c = findClass(name);// this is the defining class loader; record the stats// 5. 記錄耗時sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

結合上面的源碼,簡單總結一下雙親委派模型的執行流程:

  • 如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,會首先判斷當前類是否被加載過。已經被加載的類會直接返回,否則才會嘗試加載(每個父類加載器都會走一遍這個流程)。
  • 類加載器在進行類加載的時候,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成(調用父加載器 loadClass()方法來加載類)。這樣的話,所有的請求最終都會傳送到頂層的啟動類加載器 BootstrapClassLoader 中。
  • 只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載(調用自己的 findClass() 方法來加載類)。
  • 如果子類加載器也無法加載這個類,那么它會拋出一個 ClassNotFoundException 異常。

雙親委派模型的好處

雙親委派模型是 Java 類加載機制的重要組成部分,它通過委派父加載器優先加載類的方式,實現了兩個關鍵的安全目標:避免類的重復加載和防止核心 API 被篡改

JVM 區分不同類的依據是類名加上加載該類的類加載器,即使類名相同,如果由不同的類加載器加載,也會被視為不同的類。 雙親委派模型確保核心類總是由 BootstrapClassLoader 加載,保證了核心類的唯一性。

例如,當應用程序嘗試加載 java.lang.Object 時,AppClassLoader 會首先將請求委派給 ExtClassLoader,ExtClassLoader 再委派給 BootstrapClassLoader。BootstrapClassLoader 會在 JRE 核心類庫中找到并加載 java.lang.Object,從而保證應用程序使用的是 JRE 提供的標準版本。

同時即使攻擊者繞過了雙親委派模型,Java 仍然具備更底層的安全機制來保護核心類庫。ClassLoader 的 preDefineClass 方法會在定義類之前進行類名校驗。任何以 “java.” 開頭的類名都會觸發 SecurityException,阻止惡意代碼定義或加載偽造的核心類。

打破雙親委派模型方法

重寫 loadClass() 方法打破雙親委派模型,
原因:類加載器在進行類加載的時候,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成(調用父加載器 loadClass()方法來加載類)。

重寫 loadClass()方法之后,我們就可以改變傳統雙親委派模型的執行流程。例如,子類加載器可以在委派給父類加載器之前,先自己嘗試加載這個類,或者在父類加載器返回之后,再嘗試從其他地方加載這個類。具體的規則由我們自己實現,根據項目需求定制化。

我們比較熟悉的 Tomcat 服務器為了能夠優先加載 Web 應用目錄下的類,然后再加載其他目錄下的類,就自定義了類加載器 WebAppClassLoader 來打破雙親委托機制。這也是 Tomcat 下 Web 應用之間的類實現隔離的具體原理。

Tomcat 的類加載器的層次結構如下:
在這里插入圖片描述
從圖中的委派關系中可以看出:

  • CommonClassLoader作為 CatalinaClassLoader 和 SharedClassLoader 的父加載器。CommonClassLoader 能加載的類都可以被 CatalinaClassLoader 和 SharedClassLoader 使用。因此,CommonClassLoader 是為了實現公共類庫(可以被所有 Web 應用和 Tomcat 內部組件使用的類庫)的共享和隔離。
  • CatalinaClassLoader 和 SharedClassLoader 能加載的類則與對方相互隔離。CatalinaClassLoader 用于加載 Tomcat 自身的類,為了隔離 Tomcat 本身的類和 Web 應用的類。SharedClassLoader 作為 WebAppClassLoader 的父加載器,專門來加載 Web 應用之間共享的類比如 Spring、Mybatis。
  • 每個 Web 應用都會創建一個單獨的 WebAppClassLoader,并在啟動 Web 應用的線程里設置線程上下文類加載器為 WebAppClassLoader。各個 WebAppClassLoader 實例之間相互隔離,進而實現 Web 應用之間的類隔。

單純依靠自定義類加載器沒辦法滿足某些場景的要求,例如,有些情況下,高層的類加載器需要加載低層的加載器才能加載的類。

比如,假設我們的項目中有 Spring 的 jar 包,由于其是 Web 應用之間共享的,因此會由 SharedClassLoader 加載(Web 服務器是 Tomcat)。

我們項目中有一些用到了 Spring 的業務類,比如實現了 Spring 提供的接口、用到了 Spring 提供的注解。所以,加載 Spring 的類加載器(也就是 SharedClassLoader)也會用來加載這些業務類。

但是業務類在 Web 應用目錄下,不在 SharedClassLoader 的加載路徑下,所以 SharedClassLoader 無法找到業務類,也就無法加載它們。

如何解決這個問題呢? 這個時候就需要用到 線程上下文類加載器(ThreadContextClassLoader) 了。

6.線程上下文類加載器

  • 拿 Spring 這個例子來說,當 Spring 需要加載業務類的時候,它不是用自己的類加載器,而是用當前線程的上下文類加載器。
  • 因為每個 Web 應用都會創建一個單獨的 WebAppClassLoader,并在啟動 Web 應用的線程里設置線程上下文類加載器為 WebAppClassLoader。
  • 這樣就可以讓高層的類加載器(SharedClassLoader)借助子類加載器( WebAppClassLoader)來加載業務類,破壞了 Java 的類加載委托機制,讓應用逆向使用類加載器。

線程上下文類加載器的原理是將一個類加載器保存在線程私有數據里,跟線程綁定,然后在需要的時候取出來使用。這個類加載器通常是由應用程序或者容器(如 Tomcat)設置的
Java.lang.Thread 中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)分別用來獲取和設置線程的上下文類加載器。如果沒有通過setContextClassLoader(ClassLoader cl)進行設置的話,線程將繼承其父線程的上下文類加載器。
Spring 獲取線程線程上下文類加載器的代碼如下:

cl = Thread.currentThread().getContextClassLoader();

我們在使用 JDBC 時,都需要加載 Driver 驅動,不知道你注意到沒有,不寫Class.forName("com.mysql.jdbc.Driver"),也是可以讓 com.mysql.jdbc.Driver 正確加載,原因:
java.sql.DriverManager

public class DriverManager {// 注冊驅動的集合private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();// 初始化驅動static {loadInitialDrivers();println("JDBC DriverManager initialized");}
}

先看看 DriverManager 的類加載器:

System.out.println(DriverManager.class.getClassLoader());

打印 null,表示它的類加載器是 Bootstrap ClassLoader,會到 JAVA_HOME/jre/lib 下搜索類,但 JAVA_HOME/jre/lib 下顯然沒有 mysql-connector-java-5.1.47.jar 包,這樣問題來了,在DriverManager 的靜態代碼塊中,怎么能正確加載 com.mysql.jdbc.Driver 呢?

繼續看 loadInitialDrivers() 方法:

private static void loadInitialDrivers() {String drivers;try {drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {return System.getProperty("jdbc.drivers");}});} catch (Exception ex) {drivers = null;}// 1.使用 ServiceLoader 機制加載驅動,即 SPIAccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});println("DriverManager.initialize: jdbc.drivers = " + drivers);// 2.使用 jdbc.drivers 定義的驅動名加載驅動if (drivers == null || drivers.equals("")) {return;}String[] driversList = drivers.split(":");println("number of Drivers:" + driversList.length);for (String aDriver : driversList) {try {println("DriverManager.Initialize: loading " + aDriver);// 這里的 ClassLoader.getSystemClassLoader() 就是應用程序類加載器Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println("DriverManager.Initialize: load failed: " + ex);}}
}

先看 2. 發現它最后是使用 Class.forName 完成類的加載和初始化,關聯的是應用程序類加載器,因此可以順利完成類加載
再看 1. 它就是大名鼎鼎的 Service Provider Interface (SPI)
約定如下,在 jar 包的 META-INF/services 包下,以接口全限定名名為文件,文件內容是實現類名稱
在這里插入圖片描述
這樣就可以使用以下代碼

ServiceLoader<接口類型> allImpls = ServiceLoader.load(接口類型.class);
Iterator<接口類型> iter = allImpls.iterator();
while(iter.hasNext()) {iter.next();
}

來得到實現類,體現的是【面向接口編程+解耦】的思想,在下面一些框架中都運用了此思想:

  • JDBC
  • Servlet 初始化器
  • Spring 容器
  • Dubbo(對 SPI 進行了擴展)

接著看 ServiceLoader.load 方法:

public static <S> ServiceLoader<S> load(Class<S> service) {// 獲取線程上下文類加載器(其實是應用程序類加載器)ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);
}

線程上下文類加載器是當前線程使用的類加載器,默認就是應用程序類加載器,它內部又是由Class.forName 調用了線程上下文類加載器完成類加載,具體代碼在 ServiceLoader 的內部類LazyIterator 中:
java.util.ServiceLoader.LazyIterator#nextService

private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service,"Provider " + cn + " not found");}if (!service.isAssignableFrom(c)) {fail(service,"Provider " + cn  + " not a subtype");}try {S p = service.cast(c.newInstance());providers.put(cn, p);return p;} catch (Throwable x) {fail(service,"Provider " + cn + " could not be instantiated",x);}throw new Error();          // This cannot happen
}

相關文章:
JVM內存結構

  • JVM內存結構筆記01-運行時數據區域
  • JVM內存結構筆記02-堆
  • JVM內存結構筆記03-方法區
  • JVM內存結構筆記04-字符串常量池
  • JVM內存結構筆記05-直接內存
  • JVM內存結構筆記06-HotSpot虛擬機對象探秘
  • JVM中常量池和運行時常量池、字符串常量池三者之間的關系

JVM垃圾回收

  • JVM垃圾回收筆記01-垃圾回收算法
  • JVM垃圾回收筆記02-垃圾回收器

JVM類加載與字節碼

  • JVM類文件結構詳解
  • JVM類加載過程詳解
  • JVM類加載器詳解

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899574.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899574.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899574.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Chapters 15 16:What Is Architecture?Independence_《clean architecture》notes

What Is Architecture?&Independence **Chapter 15: What Is Architecture?****Key Concepts**:**Code Example: Layered Architecture**: **Chapter 16: Independence****Key Concepts**:**Code Example: Dependency Inversion & Interfaces**: **Combined Example:…

【SPP】RFCOMM 層在SPP中互操作性要求深度解析

藍牙串口協議&#xff08;SPP&#xff09;通過 RFCOMM 協議實現 RS232 串口仿真&#xff0c;其互操作性是設備互聯的關鍵。本文基于藍牙核心規范&#xff0c;深度解析 RFCOMM 層的能力矩陣、信號處理、流控機制及實戰開發&#xff0c;結合狀態機、流程圖和代碼示例&#xff0c;…

阻塞式IO與非阻塞IO的區別

阻塞式IO與非阻塞IO的區別 1. 阻塞式IO (Blocking I/O) 定義 當程序發起一個I/O操作&#xff08;如讀取文件、網絡數據&#xff09;時&#xff0c;進程會被掛起&#xff08;阻塞&#xff09;&#xff0c;直到操作完成或超時才會繼續執行后續代碼。在此期間&#xff0c;程序無法…

Gossip協議:分布式系統中的“八卦”傳播藝術

目錄 一、 什么是Gossip協議&#xff1f;二、 Gossip協議的應用 &#x1f4a1;三、 Gossip協議消息傳播模式詳解 &#x1f4da;四、 Gossip協議的優缺點五、 總結&#xff1a; &#x1f31f;我的其他文章也講解的比較有趣&#x1f601;&#xff0c;如果喜歡博主的講解方式&…

【C++初階】----模板初階

1.泛型函數 泛型編程&#xff1a;編寫與類型無關的通用代碼&#xff0c;是代碼復用的一種手段。模板是泛型編程的基礎。 2.函數模板 2.1函數模板的概念 函數模板代表了一個函數家族&#xff0c;該函數模板與類型無關&#xff0c;在使用時被參數化&#xff0c;根據實參類型…

git-- github的使用--賬戶和本地連接

以下指令在git 執行bash 流程&#xff1a;先看有沒有密鑰&#xff1b; 沒有的話&#xff0c;在電腦生成密鑰對&#xff0c;公鑰復制到github&#xff1b; 要想使用https&#xff0c;配置令牌&#xff0c;注意令牌有期限問題&#xff0c;連接不了有可能是期限問題 一個電腦對…

OTN(Optical Transport Network)詳解

OTN&#xff08;光傳送網&#xff09;是一種基于**波分復用&#xff08;WDM&#xff09;**的大容量光傳輸技術&#xff0c;結合了SDH的運維管理優勢和WDM的高帶寬特性&#xff0c;廣泛應用于骨干網、城域核心層及數據中心互聯&#xff08;DCI&#xff09;。 1. OTN 的基本概念 …

Python 中列表(List)、元組(Tuple)、集合(Set)和字典(Dict)四大數據結構的完整對比

以下是 Python 中列表&#xff08;List&#xff09;、元組&#xff08;Tuple&#xff09;、集合&#xff08;Set&#xff09;和字典&#xff08;Dict&#xff09;四大數據結構的完整對比分析&#xff0c;結合了核心特性、操作方式和應用場景的深度總結&#xff1a; 一、核心特性…

Angular由一個bug說起之十五:自定義基于Overlay的Tooltip

背景 工具提示&#xff08;tooltip&#xff09;是一個常見的 UI 組件&#xff0c;用于在用戶與頁面元素交互時提供額外的信息。由于angular/material/tooltip的matTooltip只能顯示純文本&#xff0c;所以我們可以通過自定義Directive來實現一個靈活且功能豐富的tooltip Overlay…

軟件工程面試題(十五)

1、servlet 創建過程以及ruquest,response,session的生命周期? Servlet的創建過程: 第一步 public class AAA extends HttpServlet{ 實現對應的doxxx方法 } 第二步: 在web.xml中配置 <servlet> <servlet-name></servlet-name> <servlet-c…

搭建QNX Software Center的Docker環境

背景 本人使用 Ubuntu Server 22.04 服務器&#xff0c;所以沒有圖形界面&#xff0c;而 QNX Software Center 需要圖形界面。為了保證服務器環境的整理&#xff0c;計劃使用Docker部署QNX Software Center 一瓶安裝圖形界面。本方既是實現方案的記錄。 資源 Dockerfile&…

C#/.NET/.NET Core技術前沿周刊 | 第 31 期(2025年3.17-3.23)

前言 C#/.NET/.NET Core技術前沿周刊&#xff0c;你的每周技術指南針&#xff01;記錄、追蹤C#/.NET/.NET Core領域、生態的每周最新、最實用、最有價值的技術文章、社區動態、優質項目和學習資源等。讓你時刻站在技術前沿&#xff0c;助力技術成長與視野拓寬。 歡迎投稿、推薦…

粘包問題解決方案

粘包問題詳解&#xff1a;TCP協議中的常見問題及Go語言解決方案 一、什么是粘包問題&#xff1f; 粘包問題是指在TCP通信中&#xff0c;發送方發送的多個獨立消息在接收方被合并成一個消息接收的現象。換句話說&#xff0c;發送方發送的多條消息在接收方被“粘”在一起&#…

vue:突然發現onok無法使用

const that this;this.$confirm({title: "修改商品提示",content: "如果當前商品存在于商品活動庫&#xff0c;則在商品活動庫的狀態會下架",onOk: function () {that.submitForm();}}); 突然發現 this.$confirm無法進入onok 最終發現是主題沖突&#x…

redis hashtable 的sizemask理解

在 Redis 的哈希表實現中&#xff0c;index hash & dict->ht[0].sizemask 是計算鍵值對應存儲位置的核心操作。這個操作看起來簡單&#xff0c;但背后涉及哈希表的內存布局和性能優化策略。我們通過以下步驟逐步解析其原理&#xff1a; 一、哈希表的設計目標 快速定位…

Ruby 命令行選項

Ruby 命令行選項 概述 Ruby 是一種廣泛使用的編程語言,它擁有強大的命令行工具,可以幫助開發者進行各種任務。了解 Ruby 的命令行選項對于提高開發效率至關重要。本文將詳細介紹 Ruby 的常用命令行選項,幫助開發者更好地利用 Ruby 的命令行功能。 Ruby 命令行選項概述 R…

【STM32】WDG看門狗(學習筆記)

學習來源----->江協科技STM32 WDG簡介 WDG&#xff08;Watchdog&#xff09;看門狗看門狗可以監控程序的運行狀態&#xff0c;當程序因為設計漏洞、硬件故障、電磁干擾等原因&#xff0c;出現卡死或跑飛現象時&#xff0c;看門狗能及時復位程序&#xff0c;避免程序陷入長…

Java 數據庫連接池

HikariCP 老外開源的。 Spring Boot 2 之后默認選擇的連接池。 號稱性能最快的數據庫連接池。 為什么性能好呢&#xff1f; ● 字節碼級別的優化-盡量的利用 JIT 的內聯手段 ● 字節碼級別的優化-利用更容易被 JVM 優化的指令 ● 代碼級別的優化-利用改造后的 FastList 代替…

Spring Boot中@Valid 與 @Validated 注解的詳解

Spring Boot中Valid 與 Validated 注解的詳解 引言Valid注解功能介紹使用場景代碼樣例 Validated注解功能介紹使用場景代碼樣例 Valid與Validated的區別結論 引言 在Spring Boot應用中&#xff0c;參數校驗是確保數據完整性和一致性的重要手段。Valid和Validated注解是Spring …

C++搜索

功能擴展說明&#xff1a; 圖類封裝&#xff1a;將圖數據結構封裝為類&#xff0c;提高代碼復用性 最短路徑查找&#xff1a;基于BFS實現未加權圖的最短路徑查找 路徑重構&#xff1a;通過parent數組回溯構建完整路徑 異常處理&#xff1a;當路徑不存在時返回空向量 復雜度分析…