1.1 .執行緒
? 這里所說的執行緒指程式執行程序中的一個執行緒物體,JVM 允許一個應用并發執行多個執行緒,Hotspot JVM 中的 Java 執行緒與原生作業系統執行緒有直接的映射關系,當執行緒本地存盤、緩沖區分配、同步物件、堆疊、程式計數器等準備好以后,就會創建一個作業系統原生執行緒,Java 執行緒結束,原生執行緒隨之被回收,作業系統負責調度所有執行緒,并把它們分配到任何可用的 CPU 上,當原生執行緒初始化完畢,就會呼叫 Java 執行緒的 run() 方法,當執行緒結束時,會釋放原生執行緒和 Java 執行緒的所有資源,
Hotspot JVM 后臺運行的系統執行緒主要有下面幾個:
| 型別 | 說明 |
|---|---|
| 虛擬機執行緒 (VM thread) | 這個執行緒等待 JVM 到達安全點操作出現,這些操作必須要在獨立的執行緒里執行,因為當 堆修改無法進行時,執行緒都需要 JVM 位于安全點,這些操作的型別有:stop-theworld 垃圾回收、執行緒堆疊 dump、執行緒暫停、執行緒偏向鎖(biased locking)解除, |
| 周期性任務執行緒 | 這執行緒負責定時器事件(也就是中斷),用來調度周期性操作的執行, |
| GC 執行緒 | 這些執行緒支持 JVM 中不同的垃圾回識訓動, |
| 編譯器執行緒 | 這些執行緒在運行時將位元組碼動態編譯成本地平臺相關的機器碼, |
| 信號分發執行緒 | 這個執行緒接收發送到 JVM 的信號并呼叫適當的 JVM 方法處理, |
1.2.JVM記憶體區域
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wt4tv5iY-1652355301086)(../images/image-20220306175928192.png)]](https://img.uj5u.com/2022/06/24/312688241609241.png)
? JVM 記憶體區域主要分為執行緒私有區域【程式計數器、虛擬機堆疊、本地方法區】、執行緒共享區 域【JAVA 堆、方法區】、直接記憶體, 執行緒私有資料區域生命周期與執行緒相同, 依賴用戶執行緒的啟動/結束 而 創建/銷毀(在 Hotspot VM 內, 每個執行緒都與作業系統的本地執行緒直接映射, 因此這部分記憶體區域的存/否跟隨本地執行緒的 生/死對應), 13/04/2018 Page 22 of 283 執行緒共享區域隨虛擬機的啟動/關閉而創建/銷毀, 直接記憶體并不是 JVM 運行時資料區的一部分, 但也會被頻繁的使用: 在 JDK 1.4 引入的 NIO 提 供了基于 Channel 與 Buffer 的 IO 方式, 它可以使用 Native 函式庫直接分配堆外記憶體, 然后使用 DirectByteBuffer 物件作為這塊記憶體的參考進行操作(詳見: Java I/O 擴展), 這樣就避免了在 Java 堆和 Native 堆中來回復制資料, 因此在一些場景中可以顯著提高性能,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FQOpbGTA-1652355301088)(../images/1.png)]](https://img.uj5u.com/2022/06/24/312688241609242.png)
1.2.1.程式計數器(執行緒私有)
? 一塊較小的記憶體空間, 是當前執行緒所執行的位元組碼的行號指示器,每條執行緒都要有一個獨立的程式計數器,這類記憶體也稱為“執行緒私有”的記憶體,正在執行 java 方法的話,計數器記錄的是虛擬機位元組碼指令的地址(當前指令的地址),如果還是 Native 方法,則為空,
這個記憶體區域是唯一一個在虛擬機中沒有規定任何OutOfMemoryError 情況的區域,
1.2.2.虛擬機堆疊(執行緒私有)
? 是描述java 方法執行的記憶體模型,每個方法在執行的同時都會創建一個堆疊幀(Stack Frame) 用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等資訊,每一個方法從呼叫直至執行完成的程序,就對應著一個堆疊幀在虛擬機堆疊中入堆疊到出堆疊的程序,
堆疊幀( Frame)是用來存盤資料和部分程序結果的資料結構,同時也被用來處理動態鏈接(Dynamic Linking)、 方法回傳值和例外分派( Dispatch Exception),堆疊幀隨著方法呼叫而創建,隨著方法結束而銷毀——無論方法是正常完成還是例外完成(拋出了在方法內未被捕獲的例外)都算作方法結束,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uskwvGRH-1652355301088)(../images/2.png)]](https://img.uj5u.com/2022/06/24/312688241609243.png)
1.2.3.本地方法區(執行緒私有)
? 本地方法區和Java Stack作用類似, 區別是虛擬機堆疊為執行Java方法服務, 而本地方法堆疊則為Native方法服務, 如果一個VM實作使用C-linkage模型來支持Native呼叫, 那么該堆疊將會是一個C堆疊,但HotSpot VM直接就把本地方法堆疊和虛擬機堆疊合二為一,
1.2.4.堆(Heap-執行緒共享)-運行時資料區
? 是被執行緒共享的一塊記憶體區域,創建的物件和陣列都保存在Java堆記憶體中,也是垃圾收集器進行垃圾收集的最重要的記憶體區域,由于現代VM采用分代收集演算法, 因此Java堆從GC的角度還可以細分為:新生代(Eden區、From Survivor區和To Survivor區)和老年代,
1.2.5.方法區/永久代(執行緒共享)
? 即我們常說的永久代(Permanent Generation), 用于存盤被JVM加載的類資訊、常量、靜態變數、即時編譯器編譯后的代碼等資料. HotSpot VM把GC分代收集擴展至方法區, 即使用Java堆的永久代來實作方法區, 這樣HotSpot的垃圾收集器就可以像管理Java堆一樣管理這部分記憶體, 而不必為方法區開發專門的記憶體管理器(永久帶的記憶體回收的主要目標是針對常量池的回收和型別的卸載, 因此收益一般很小),運行時常量池(Runtime Constant Pool)是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述等資訊外,還有一項資訊是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號參考,這部分內容將在類加載后存放到方法區的運行時常量池中,Java虛擬機對Class檔案的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個位元組用于存盤哪種資料都必須符合規范上的要求,這樣才會被虛擬機認可、裝載和執行,
1.3.JVM運行時記憶體
? Java堆從GC的角度還可以細分為:新生代(Eden區、From Survivor區和To Survivor區)和老年代,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EDZllVXN-1652355301090)(../images/3.png)]](https://img.uj5u.com/2022/06/24/312688241609244.png)
1.3.1.新生代
? 是用來存放新生的物件,一般占據堆的 1/3 空間,由于頻繁創建物件,所以新生代會頻繁觸發MinorGC 進行垃圾回收,新生代又分為 Eden 區、ServivorFrom、ServivorTo 三個區,
1.3.1.Eden區
? Java 新物件的出生地(如果新創建的物件占用記憶體很大,則直接分配到老年代),當Eden 區記憶體不夠的時候就會觸發 MinorGC,對新生代區進行一次垃圾回收,
1.3.2.ServivorFrom
? 上一次 GC 的幸存者,作為這一次 GC 的被掃描者,
1.3.3.ServivorTo
? 保留了一次 MinorGC 程序中的幸存者,
1.3.4.MinorGC 的程序(復制->清空->互換)
? MinorGC 采用復制演算法,
1:eden、servicorFrom 復制到ServicorTo,年齡+1首先,把Eden和ServivorFrom區域中存活的物件復制到ServicorTo區域(如果有物件的年齡以及達到了老年的標準,則賦值到老年代區),同時把這些物件的年齡+1(如果ServicorTo不夠位置了就放到老年區);
2:清空eden、servicorFrom然后,清空Eden和ServicorFrom中的物件;
3:ServicorTo和ServicorFrom互換最后,ServicorTo和ServicorFrom互換,原ServicorTo成為下一次GC時的ServicorFrom區,
1.3.2.老年代
? 主要存放應用程式中生命周期長的記憶體物件,老年代的物件比較穩定,所以MajorGC不會頻繁執行,在進行MajorGC前一般都先進行了一次MinorGC,使得有新生代的物件晉身入老年代,導致空間不夠用時才觸發,當無法找到足夠大的連續空間分配給新創建的較大物件時也會提前觸發一次MajorGC進行垃圾回收騰出空間,MajorGC采用標記清除演算法:首先掃描一次所有老年代,標記出存活的物件,然后回收沒有標記的物件,MajorGC的耗時比較長,因為要掃描再回收,MajorGC會產生記憶體碎片,為了減少記憶體損耗,我們一般需要進行合并或者標記出來方便下次直接分配,當老年代也滿了裝不下的時候,就會拋出OOM(Out of Memory)例外,
1.3.3.永久代
? 指記憶體的永久保存區域,主要存放Class和Meta(元資料)的資訊,Class在被加載的時候被放入永久區域,它和和存放實體的區域不同,GC不會在主程式運行期對永久區域進行清理,所以這也導致了永久代的區域會隨著加載的Class的增多而脹滿,最終拋出OOM例外,
1.3.3.1.JAVA8與元資料
? 在Java8中,永久代已經被移除,被一個稱為“元資料區”(元空間)的區域所取代,元空間的本質和永久代類似,元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本 地 內 存,因此,默認情況下,元空間的大小僅受本地記憶體限制,類 的 元 數 據 放 入native memory, 字串池和類的靜態變數放入java堆中,這樣可以加載多少類的元資料就不再由MaxPermSize控制, 而由系統的實際可用空間來控制,
1.4.垃圾回收與演算法
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UEGOZQES-1652355301093)(../images/4.png)]](https://img.uj5u.com/2022/06/24/312688241609245.png)
1.4.1.如何確定垃圾
1.4.1.1.參考計數法
? 在Java中,參考和物件是有關聯的,如果要操作物件則必須用參考進行,因此,很顯然一個簡單的辦法是通過參考計數來判斷一個物件是否可以回收,簡單說,即一個物件如果沒有任何與之關聯的參考,即他們的參考計數都不為0,則說明物件不太可能再被用到,那么這個物件就是可回收物件,
1.4.1.2.可達性分析
? 為了解決參考計數法的回圈參考問題,Java使用了可達性分析的方法,通過一系列的“GC roots”物件作為起點搜索,如果在“GC roots”和一個物件之間沒有可達路徑,則稱該物件是不可達的,要注意的是,不可達物件不等價于可回收物件,不可達物件變為可回收物件至少要經過兩次標記程序,兩次標記后仍然是可回收物件,則將面臨回收,
1.4.2.標記清除演算法(Mark-Sweep)
? 最基礎的垃圾回收演算法,分為兩個階段,標注和清除,標記階段標記出所有需要回收的物件,清除階段回收被標記的物件所占用的空間,如圖從圖中我們就可以發現,該演算法最大的問題是記憶體碎片化嚴重,后續可能發生大物件不能找到可利用空間的問題,2.4.3.復制演算法(copying)為了解決Mark-Sweep演算法記憶體碎片化的缺陷而被提出的演算法,按記憶體容量將記憶體劃分為等大小的兩塊,每次只使用其中一塊,當這一塊記憶體滿后將尚存活的物件復制到另一塊上去,把已使用的記憶體清掉,如圖:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KXL46KTw-1652355301094)(../images/5.png)]](https://img.uj5u.com/2022/06/24/312688241609246.png)
從圖中我們就可以發現,該演算法最大的問題是記憶體碎片化嚴重,后續可能發生大物件不能找到可利用空間的問題,
1.4.3復制演算法(copying)
為了解決 Mark-Sweep 演算法記憶體碎片化的缺陷而被提出的演算法,按記憶體容量將記憶體劃分為等大小的兩塊,每次只使用其中一塊,當這一塊記憶體滿后將尚存活的物件復制到另一塊上去,把已使用的記憶體清掉,如圖:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bH6o95GE-1652355301094)(../images/6.png)]](https://img.uj5u.com/2022/06/24/312688241609247.png)
? 這種演算法雖然實作簡單,記憶體效率高,不易產生碎片,但是最大的問題是可用記憶體被壓縮到了原本的一半,且存活物件增多的話,Copying演算法的效率會大大降低,
1.4.4.標記整理演算法(Mark-Compact)
? 結合了以上兩個演算法,為了避免缺陷而提出,標記階段和Mark-Sweep演算法相同,標記后不是清理物件,而是將存活物件移向記憶體的一端,然后清除端邊界外的物件,如圖:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fFA9WI20-1652355301094)(../images/7.png)]](https://img.uj5u.com/2022/06/24/312688241609248.png)
1.4.5.分代收集演算法
? 分代收集法是目前大部分JVM 所采用的方法,其核心思想是根據物件存活的不同生命周期將記憶體劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代(Young Generation),老生代的特點是每次垃圾回收時只有少量物件需要被回收,新生代的特點是每次垃圾回收時都有大量垃圾需要被回收,因此可以根據不同區域選擇不同的演算法,
1.4.1.1. 新生代與復制演算法
? 目前大部分 JVM 的 GC 對于新生代都采取Copying 演算法,因為新生代中每次垃圾回收都要回收大部分物件,即要復制的操作比較少,但通常并不是按照 1:1 來劃分新生代,一般將新生代劃分為一塊較大的Eden 空間和兩個較小的 Survivor 空間(From Space, To Space),每次使用Eden 空間和其中的一塊Survivor 空間,當進行回收時,將該兩塊空間中還存活的物件復制到另一塊 Survivor 空間中,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ecQBPNuU-1652355301095)(../images/8.png)]](https://img.uj5u.com/2022/06/24/312688241609249.png)
1.4.5.2.老年代與標記復制演算法
? 而老年代因為每次只回收少量物件,因而采用Mark-Compact演算法,
? 1.JAVA虛擬機提到過的處于方法區的永生代(Permanet Generation),它用來存盤class類,常量,方法描述等,對永生代的回收主要包括廢棄常量和無用的類,
? 2.物件的記憶體分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放物件的那一塊),少數情況會直接分配到老生代,
? 3.當新生代的Eden Space和From Space空間不足時就會發生一次GC,進行GC后,Eden Space和From Space區的存活物件會被挪到To Space,然后將Eden Space和From Space進行清理,
? 4.如果To Space無法足夠存盤某個物件,則將這個物件存盤到老生代,
? 5.在進行GC后,使用的便是Eden Space和To Space了,如此反復回圈,
? 6.當物件在Survivor區躲過一次GC后,其年齡就會+1,默認情況下年齡到達15的物件會被移到老生代中,
1.5.JAVA 四中參考型別
1.5.1.強參考
? 在Java中最常見的就是強參考,把一個物件賦給一個參考變數,這個參考變數就是一個強參考,當一個物件被強參考變數參考時,它處于可達狀態,它是不可能被垃圾回識訓制回收的,即使該物件以后永遠都不會被用到JVM也不會回收,因此強參考是造成Java記憶體泄漏的主要原因之一,
1.5.2.軟參考軟
? 參考需要用SoftReference類來實作,對于只有軟參考的物件來說,當系統記憶體足夠時它不會被回收,當系統記憶體空間不足時它會被回收,軟參考通常用在對記憶體敏感的程式中,
1.5.3.弱參考
? 弱參考需要用WeakReference類來實作,它比軟參考的生存期更短,對于只有弱參考的物件來說,只要垃圾回識訓制一運行,不管JVM的記憶體空間是否足夠,總會回收該物件占用的記憶體,
1.5.4.虛參考
? 虛參考需要PhantomReference類來實作,它不能單獨使用,必須和參考佇列聯合使用,虛參考的主要作用是跟蹤物件被垃圾回收的狀態,
1.6.GC分代收集演算法VS 磁區收集演算法
1.6.1.分代收集演算法
? 當前主流VM垃圾收集都采用”分代收集”(Generational Collection)演算法, 這種演算法會根據物件存活周期的不同將記憶體劃分為幾塊, 如JVM中的新生代、老年代、永久代,這樣就可以根據各年代特點分別采用最適當的GC演算法
1.6.1.1.在新生代-復制演算法
? 每次垃圾收集都能發現大批物件已死, 只有少量存活. 因此選用復制演算法,只需要付出少量存活物件的復制成本就可以完成收集.
1.6.1.2.在老年代-標記整理演算法
? 因為物件存活率高、沒有額外空間對它進行分配擔保, 就必須采用“標記—清理”或“標記—整理”演算法來進行回收,不必進行記憶體復制, 且直接騰出空閑記憶體.
1.6.2.磁區收集演算法
? 磁區演算法則將整個堆空間劃分為連續的不同小區間, 每個小區間獨立使用, 獨立回收. 這樣做的好處是可以控制一次回收多少個小區間, 根據目標停頓時間, 每次合理地回收若干個小區間(而不是整個堆), 從而減少一次GC所產生的停頓,
1.7.GC垃圾收集器
? Java堆記憶體被劃分為新生代和年老代兩部分,新生代主要使用復制和標記-清除垃圾回收演算法;年老代主要使用標記-整理垃圾回收演算法,因此java虛擬中針對新生代和年老代分別提供了多種不同的垃圾收集器,JDK1.6中Sun HotSpot虛擬機的垃圾收集器如下:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TpTvLHDP-1652355301095)(../images/9.png)]](https://img.uj5u.com/2022/06/24/3126882416092410.png)
1.7.1.Serial垃圾收集器(單執行緒、復制演算法)
? Serial(英文連續)是最基本垃圾收集器,使用復制演算法,曾經是JDK1.3.1之前新生代唯一的垃圾收集器,Serial是一個單執行緒的收集器,它不但只會使用一個CPU或一條執行緒去完成垃圾收集作業,并且在進行垃圾收集的同時,必須暫停其他所有的作業執行緒,直到垃圾收集結束,Serial垃圾收集器雖然在收集垃圾程序中需要暫停所有其他的作業執行緒,但是它簡單高效,對于限定單個CPU環境來說,沒有執行緒互動的開銷,可以獲得最高的單執行緒垃圾收集效率,因此Serial垃圾收集器依然是java虛擬機運行在Client模式下默認的新生代垃圾收集器,
1.7.2.ParNew垃圾收集器(Serial+多執行緒)
? ParNew垃圾收集器其實是Serial收集器的多執行緒版本,也使用復制演算法,除了使用多執行緒進行垃圾收集之外,其余的行為和Serial收集器完全一樣,ParNew垃圾收集器在垃圾收集程序中同樣也要暫停所有其他的作業執行緒,ParNew收集器默認開啟和CPU數目相同的執行緒數,可以通過-XX:ParallelGCThreads引數來限制垃圾收集器的執行緒數,【Parallel:平行的】ParNew雖然是除了多執行緒外和Serial收集器幾乎完全一樣,但是ParNew垃圾收集器是很多java虛擬機運行在Server模式下新生代的默認垃圾收集器,
1.7.3.Parallel Scavenge收集器(多執行緒復制演算法、高效)
? Parallel Scavenge收集器也是一個新生代垃圾收集器,同樣使用復制演算法,也是一個多執行緒的垃圾收集器,它重點關注的是程式達到一個可控制的吞吐量(Thoughput,CPU用于運行用戶代碼的時間/CPU總消耗時間,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)),高吞吐量可以最高效率地利用CPU時間,盡快地完成程式的運算任務,主要適用于在后臺運算而不需要太多互動的任務,自適應調節策略也是ParallelScavenge收集器與ParNew收集器的一個重要區別,
1.7.4.Serial Old收集器(單執行緒標記整理演算法)
? Serial Old是Serial垃圾收集器年老代版本,它同樣是個單執行緒的收集器,使用標記-整理演算法,這個收集器也主要是運行在Client默認的java虛擬機默認的年老代垃圾收集器,在Server模式下,主要有兩個用途:1.在JDK1.5之前版本中與新生代的Parallel Scavenge收集器搭配使用,2.作為年老代中使用CMS收集器的后備垃圾收集方案,新生代Serial與年老代Serial Old搭配垃圾收集程序圖:新生代Parallel Scavenge收集器與ParNew收集器作業原理類似,都是多執行緒的收集器,都使用的是復制演算法,在垃圾收集程序中都需要暫停所有的作業執行緒,新生代Parallel Scavenge/ParNew與年老代Serial Old搭配垃圾收集程序圖:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WchrjpM5-1652355301096)(../images/10.png)]](https://img.uj5u.com/2022/06/24/3126882416092411.png)
? 新生代 Parallel Scavenge 收集器與 ParNew 收集器作業原理類似,都是多執行緒的收集器,都使用的是復制演算法,在垃圾收集程序中都需要暫停所有的作業執行緒,新生代Parallel Scavenge/ParNew 與年老代 Serial Old 搭配垃圾收集程序圖:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GFbaxz13-1652355301096)(../images/11.png)]](https://img.uj5u.com/2022/06/24/3126882416092412.png)
1.7.5.Parallel Old收集器(多執行緒標記整理演算法)
? Parallel Old收集器是Parallel Scavenge的年老代版本,使用多執行緒的標記-整理演算法,在JDK1.6才開始提供,在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配年老代的Serial Old收集器,只能保證新生代的吞吐量優先,無法保證整體的吞吐量,Parallel Old正是為了在年老代同樣提供吞吐量優先的垃圾收集器,如果系統對吞吐量要求比較高,可以優先考慮新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略,新生代ParallelScavenge和年老代Parallel Old收集器搭配運行程序圖:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8oiU87a0-1652355301097)(../images/11.1.png)]](https://img.uj5u.com/2022/06/24/3126882416092413.png)
1.7.6.CMS收集器(多執行緒標記清除演算法)
? Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器,其最主要目標是獲取最短垃圾回收停頓時間,和其他年老代使用標記-整理演算法不同,它使用多執行緒的標記-清除演算法,最短的垃圾收集停頓時間可以為互動比較高的程式提高用戶體驗,CMS作業機制相比其他的垃圾收集器來說更復雜,整個程序分為以下4個階段:
1.7.6.1.初始標記
? 只是標記一下GC Roots能直接關聯的物件,速度很快,仍然需要暫停所有的作業執行緒,
1.7.6.2.并發標記
? 進行GC Roots跟蹤的程序,和用戶執行緒一起作業,不需要暫停作業執行緒,
1.7.6.3.重新標記
? 為了修正在并發標記期間,因用戶程式繼續運行而導致標記產生變動的那一部分物件的標記記錄,仍然需要暫停所有的作業執行緒,
1.7.6.4.并發清除
? 清除GC Roots不可達物件,和用戶執行緒一起作業,不需要暫停作業執行緒,由于耗時最長的并發標記和并發清除程序中,垃圾收集執行緒可以和用戶現在一起并發作業,所以總體上來看CMS收集器的記憶體回收和用戶執行緒是一起并發地執行,CMS收集器作業程序:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-SOwGT3Ds-1652355301097)(../images/13.png)]](https://img.uj5u.com/2022/06/24/3126882416092414.png)
1.7.7.G1收集器
? Garbage first垃圾收集器是目前垃圾收集器理論發展的最前沿成果,相比與CMS收集器,G1收集器兩個最突出的改進是:
1.基于標記-整理演算法,不產生記憶體碎片,
2.可以非常精確控制停頓時間,在不犧牲吞吐量前提下,實作低停頓垃圾回收,G1收集器避免全區域垃圾收集,它把堆記憶體劃分為大小固定的幾個獨立區域,并且跟蹤這些區域的垃圾收集進度,同時在后臺維護一個優先級串列,每次根據所允許的收集時間,優先回收垃圾最多的區域,區域劃分和優先級區域回識訓制,確保G1收集器可以在有限時間獲得最高的垃圾收集效率,
1.8.JAVA IO/NIO
1.8.1.阻塞IO模型
? 最傳統的一種IO模型,即在讀寫資料程序中會發生阻塞現象,當用戶執行緒發出IO請求之后,內核會去查看資料是否就緒,如果沒有就緒就會等待資料就緒,而用戶執行緒就會處于阻塞狀態,用戶執行緒交出CPU,當資料就緒之后,內核會將資料拷貝到用戶執行緒,并回傳結果給用戶執行緒,用戶執行緒才解除block狀態,典型的阻塞IO模型的例子為:data = https://www.cnblogs.com/jixia/archive/2022/06/24/socket.read();如果資料沒有就緒,就會一直阻塞在read方法,
1.8.2.非阻塞IO模型
? 當用戶執行緒發起一個read操作后,并不需要等待,而是馬上就得到了一個結果,如果結果是一個error時,它就知道資料還沒有準備好,于是它可以再次發送read操作,一旦內核中的資料準備好了,并且又再次收到了用戶執行緒的請求,那么它馬上就將資料拷貝到了用戶執行緒,然后回傳,所以事實上,在非阻塞IO模型中,用戶執行緒需要不斷地詢問內核資料是否就緒,也就說非阻塞IO不會交出CPU,而會一直占用CPU,典型的非阻塞IO模型一般如下:
while(true){
data = https://www.cnblogs.com/jixia/archive/2022/06/24/socket.read();
if(data!= error){
處理資料
break;
}
}
但是對于非阻塞IO 就有一個非常嚴重的問題,在 while 回圈中需要不斷地去詢問內核資料是否就緒,這樣會導致CPU 占用率非常高,因此一般情況下很少使用 while 回圈這種方式來讀取資料,
1.8.3.多路復用IO模型
? 多路復用IO模型是目前使用得比較多的模型,Java NIO實際上就是多路復用IO,在多路復用IO模型中,會有一個執行緒不斷去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才真正呼叫實際的IO讀寫操作,因為在多路復用IO模型中,只需要使用一個執行緒就可以管理多個socket,系統不需要建立新的行程或者執行緒,也不必維護這些執行緒和行程,并且只有在真正有socket讀寫事件進行時,才會使用IO資源,所以它大大減少了資源占用,在Java NIO中,是通過selector.select()去查詢每個通道是否有到達事件,如果沒有事件,則一直阻塞在那里,因此這種方式會導致用戶執行緒的阻塞,多路復用IO模式,通過一個執行緒就可以管理多個socket,只有當socket真正有讀寫事件發生才會占用資源來進行實際的讀寫操作,因此,多路復用IO比較適合連接數比較多的情況,另外多路復用IO為何比非阻塞IO模型的效率高是因為在非阻塞IO中,不斷地詢問socket狀態時通過用戶執行緒去進行的,而在多路復用IO中,輪詢每個socket狀態是內核在進行的,這個效率要比用戶執行緒要高的多,不過要注意的是,多路復用IO模型是通過輪詢的方式來檢測是否有事件到達,并且對到達的事件逐一進行回應,因此對于多路復用IO模型來說,一旦事件回應體很大,那么就會導致后續的事件遲遲得不到處理,并且會影響新的事件輪詢,
1.8.4.信號驅動IO模型
? 在信號驅動IO模型中,當用戶執行緒發起一個IO請求操作,會給對應的socket注冊一個信號函式,然后用戶執行緒會繼續執行,當內核資料就緒時會發送一個信號給用戶執行緒,用戶執行緒接收到信號之后,便在信號函式中呼叫IO讀寫操作來進行實際的IO請求操作,
1.8.5.異步IO模型
? 異步IO模型才是最理想的IO模型,在異步IO模型中,當用戶執行緒發起read操作之后,立刻就可以開始去做其它的事,而另一方面,從內核的角度,當它受到一個asynchronous read之后,它會立刻回傳,說明read請求已經成功發起了,因此不會對用戶執行緒產生任何block,然后,內核會等待資料準備完成,然后將資料拷貝到用戶執行緒,當這一切都完成之后,內核會給用戶執行緒發送一個信號,告訴它read操作完成了,也就說用戶執行緒完全不需要實際的整個IO操作是如何進行的,只需要先發起一個請求,當接收內核回傳的成功信號時表示IO操作已經完成,可以直接去使用資料了,也就說在異步IO模型中,IO操作的兩個階段都不會阻塞用戶執行緒,這兩個階段都是由內核自動完成,然后發送一個信號告知用戶執行緒操作已完成,用戶執行緒中不需要再次呼叫IO函式進行具體的讀寫,這點是和信號驅動模型有所不同的,在信號驅動模型中,當用戶執行緒接收到信號表示資料已經就緒,然后需要用戶執行緒呼叫IO函式進行實際的讀寫操作;而在異步IO模型中,收到信號表示IO操作已經完成,不需要再在用戶執行緒中呼叫IO函式進行實際的讀寫操作,注意,異步IO是需要作業系統的底層支持,在Java 7中,提供了Asynchronous IO,更多參考:http://www.importnew.com/19816.html
1.8.6.JAVA IO包
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2Ho3fADm-1652355301098)(../images/14.png)]](https://img.uj5u.com/2022/06/24/3126882416092415.png)
1.8.7.JAVA NIO
? NIO主要有三大核心部分:Channel(通道),Buffer(緩沖區), Selector,傳統IO基于位元組流和字符流進行操作,而NIO基于Channel和Buffer(緩沖區)進行操作,資料總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中,Selector(選擇區)用于監聽多個通道的事件(比如:連接打開,資料到達),因此,單個執行緒可以監聽多個資料通道,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IIj6YF9r-1652355301098)(../images/15.png)]](https://img.uj5u.com/2022/06/24/3126882416092416.png)
1.8.2.1.NIO的緩沖區
? Java IO面向流意味著每次從流中讀一個或多個位元組,直至讀取所有位元組,它們沒有被快取在任何地方,此外,它不能前后移動流中的資料,如果需要前后移動從流中讀取的資料,需要先將它快取到一個緩沖區,NIO的緩沖導向方法不同,資料讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動,這就增加了處理程序中的靈活性,但是,還需要檢查是否該緩沖區中包含所有您需要處理的資料,而且,需確保當更多的資料讀入緩沖區時,不要覆寫緩沖區里尚未處理的資料,
1.8.2.2.NIO的非阻塞
? IO的各種流是阻塞的,這意味著,當一個執行緒呼叫read() 或write()時,該執行緒被阻塞,直到有一些資料被讀取,或資料完全寫入,該執行緒在此期間不能再干任何事情了,NIO的非阻塞模式,使一個執行緒從某通道發送請求讀取資料,但是它僅能得到目前可用的資料,如果目前沒有資料可用時,就什么都不會獲取,而不是保持執行緒阻塞,所以直至資料變的可以讀取之前,該執行緒可以繼續做其他的事情,非阻塞寫也是如此,一個執行緒請求寫入一些資料到某通道,但不需要等待它完全寫入,這個執行緒同時可以去做別的事情,執行緒通常將非阻塞IO的空閑時間用于在其它通道上執行IO操作,所以一個單獨的執行緒現在可以管理多個輸入和輸出通道(channel),
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bMWFqSf7-1652355301098)(../images/17.png)]](https://img.uj5u.com/2022/06/24/3126882416092417.png)
1.8.8.Channel
? 首先說一下Channel,國內大多翻譯成“通道”,Channel和IO中的Stream(流)是差不多一個等級的,只不過Stream是單向的,譬如:InputStream, OutputStream,而Channel是雙向的,既可以用來進行讀操作,又可以用來進行寫操作,
NIO中的Channel的主要實作有:
? 1.FileChannel
? 2.DatagramChannel
? 3.SocketChannel
? 4.ServerSocketChannel
這里看名字就可以猜出個所以然來:分別可以對應檔案IO、UDP和TCP(Server和Client),下面演示的案例基本上就是圍繞這4個型別的Channel進行陳述的,
1.8.9.Buffer
? Buffer,故名思意,緩沖區,實際上是一個容器,是一個連續陣列,Channel提供從檔案、網路讀取資料的渠道,但是讀取或寫入的資料都必須經由Buffer,
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-N4JzCKUA-1652355301099)(../images/16.png)]
上面的圖描述了從一個客戶端向服務端發送資料,然后服務端接收資料的程序,客戶端發送資料時,必須先將資料存入Buffer中,然后將Buffer中的內容寫入通道,服務端這邊接收資料必須通過Channel將資料讀入到Buffer中,然后再從Buffer中取出資料來處理,在NIO中,Buffer是一個頂層父類,它是一個抽象類,常用的Buffer的子類有:ByteBuffer、IntBuffer、CharBuffer、LongBuffer、DoubleBuffer、FloatBuffer、ShortBuffer
1.8.10.Selector
? Selector類是NIO的核心類,Selector能夠檢測多個注冊的通道上是否有事件發生,如果有事件發生,便獲取事件然后針對每個事件進行相應的回應處理,這樣一來,只是用一個單執行緒就可以管理多個通道,也就是管理多個連接,這樣使得只有在連接真正有讀寫事件發生時,才會呼叫函式來進行讀寫,就大大地減少了系統開銷,并且不必為每個連接都創建一個執行緒,不用去維護多個執行緒,并且避免了多執行緒之間的背景關系切換導致的開銷,
1.9.JVM 類加載機制
JVM 類加載機制分為五個部分:加載,驗證,準備,決議,初始化,下面我們就分別來看一下這 五個程序,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xEMHx2Cn-1652355301099)(../images/18.png)]](https://img.uj5u.com/2022/06/24/3126882416092418.png)
1.9.1.加載
? 加載是類加載程序中的一個階段,這個階段會在記憶體中生成一個代表這個類的 java.lang.Class 物件,作為方法區這個類的各種資料的入口,注意這里不一定非得要從一個 Class 檔案獲取,這里既可以從 ZIP 包中讀取(比如從 jar 包和 war 包中讀取),也可以在運行時計算生成(動態代理),也可以由其它檔案生成(比如將 JSP 檔案轉換成對應的 Class 類),
1.9.2.驗證
? 這一階段的主要目的是為了確保 Class 檔案的位元組流中包含的資訊是否符合當前虛擬機的要求,并
且不會危害虛擬機自身的安全,
1.9.3.準備
? 準備階段是正式為類變數分配記憶體并設定類變數的初始值階段,即在方法區中分配這些變數所使
用的記憶體空間,注意這里所說的初始值概念,比如一個類變數定義為:
public static int v = 8080;
? 實際上變數 v 在準備階段過后的初始值為 0 而不是 8080,將 v 賦值為 8080 的 put static 指令是程式被編譯后,存放于類構造器
public static final int v = 8080;
? 在編譯階段會為 v 生成 ConstantValue 屬性,在準備階段虛擬機會根據 ConstantValue 屬性將 v賦值為 8080,
1.9.4. 決議
? 決議階段是指虛擬機將常量池中的符號參考替換為直接參考的程序,符號參考就是 class 檔案中 的以下型別的常量,
1. CONSTANT_Class_info
2. CONSTANT_Field_info
3. CONSTANT_Method_info
1.9.5. 符號參考
? 符號參考與虛擬機實作的布局無關,參考的目標并不一定要已經加載到記憶體中,各種虛擬 機實作的記憶體布局可以各不相同,但是它們能接受的符號參考必須是一致的,因為符號引 用的字面量形式明確定義在 Java 虛擬機規范的 Class 檔案格式中,
1.9.6. 直接參考
? 直接參考可以是指向目標的指標,相對偏移量或是一個能間接定位到目標的句柄,如果有 了直接參考,那參考的目標必定已經在記憶體中存在,
1.9.7. 初始化
? 初始化階段是類加載最后一個階段,前面的類加載階段之后,除了在加載階段可以自定義類加載 器以外,其它操作都由 JVM 主導,到了初始階段,才開始真正執行類中定義的 Java 程式代碼,
1.9.8. 類構造器
? 初始化階段是執行類構造器方法的程序,方法是由編譯器自動收集類中的類變 量的賦值操作和靜態陳述句塊中的陳述句合并而成的,虛擬機會保證子方法執行之前,父類 的方法已經執行完畢,如果一個類中沒有對靜態變數賦值也沒有靜態陳述句塊,那么編譯 器可以不為這個類生成()方法,
? 注意以下幾種情況不會執行類初始化:
1. 通過子類參考父類的靜態欄位,只會觸發父類的初始化,而不會觸發子類的初始化,
2. 定義物件陣列,不會觸發該類的初始化,
3. 常量在編譯期間會存入呼叫類的常量池中,本質上并沒有直接參考定義常量的類,不會觸 發定義常量所在的類,
4. 通過類名獲取 Class 物件,不會觸發類的初始化,
5. 通過 Class.forName 加載指定類時,如果指定引數 initialize 為 false 時,也不會觸發類初 始化,其實這個引數是告訴虛擬機,是否要對類進行初始化,
6. 通過 ClassLoader 默認的 loadClass 方法,也不會觸發初始化動作,
1.9.9. 類加載器
? 虛擬機設計團隊把加載動作放到 JVM 外部實作,以便讓應用程式決定如何獲取所需的類,JVM 提 供了 3 種類加載器:
1.9.9.1. 啟動類加載器(Bootstrap ClassLoader)
? 1. 負責加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath 引數指定路徑中的,且被 虛擬機認可(按檔案名識別,如 rt.jar)的類,
1.9.9.2. 擴展類加載器(Extension ClassLoader)
? 2. 負責加載 JAVA_HOME\lib\ext 目錄中的,或通過 java.ext.dirs 系統變數指定路徑中的類 庫,
1.9.9.3. 應用程式類加載器(Application ClassLoader):
3. 負責加載用戶路徑(classpath)上的類別庫, JVM 通過雙親委派模型進行類的加載,當然我們也可以通過繼承 java.lang.ClassLoader 實作自定義的類加載器,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UWfasEss-1652355301099)(../images/19.png)]](https://img.uj5u.com/2022/06/24/3126882416092419.png)
1.9.10.雙親委派
? 當一個類收到了類加載請求,他首先不會嘗試自己去加載這個類,而是把這個請求委派給父類去完成,每一個層次類加載器都是如此,因此所有的加載請求都應該傳送到啟動類加載其中,只有當父類加載器反饋自己無法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會嘗試自己去加載,采用雙親委派的一個好處是比如加載位于 rt.jar 包中的類 java.lang.Object,不管是哪個加載器加載這個類,最終都是委托給頂層的啟動類加載器進行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個 Object 物件,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-41MUxMMX-1652355301100)(../images/20.png)]](https://img.uj5u.com/2022/06/24/3126882416092420.png)
1.9.11. OSGI(動態模型系統)
? OSGi(Open Service Gateway Initiative),是面向 Java 的動態模型系統,是 Java 動態化模塊化系 統的一系列規范,
1.9.11.1. 動態改變構造
? OSGi 服務平臺提供在多種網路設備上無需重啟的動態改變構造的功能,為了最小化耦合度和促使 這些耦合度可管理,OSGi 技術提供一種面向服務的架構,它能使這些組件動態地發現對方,
1.9.11.2. 模塊化編程與熱插拔
? OSGi 旨在為實作 Java 程式的模塊化編程提供基礎條件,基于 OSGi 的程式很可能可以實作模塊級 的熱插拔功能,當程式升級更新時,可以只停用、重新安裝然后啟動程式的其中一部分,這對企 業級程式開發來說是非常具有傭訓力的特性, OSGi 描繪了一個很美好的模塊化開發目標,而且定義了實作這個目標的所需要服務與架構,同時 也有成熟的框架進行實作支持,但并非所有的應用都適合采用 OSGi 作為基礎架構,它在提供強大 功能同時,也引入了額外的復雜度,因為它不遵守了類加載的雙親委托模型,
上善若水任方圓,天道酬勤允齊飛!轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/495575.html
標籤:其他
