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.pswp.cn/web/80003.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80003.shtml
英文地址,請注明出處:http://en.pswp.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語…