主頁 > 後端開發 > 一篇與面試官和藹交流的深入了解JVM(JDK8)

一篇與面試官和藹交流的深入了解JVM(JDK8)

2021-01-25 06:38:25 後端開發

文章目錄

    • 1、類加載機制
    • 2、雙親委派機制(先找父親加載,不行再由兒子自己加載)
        • 2.1、類加載器
        • 2.2、加載器初始化程序
        • 2.3、雙親委派機制
        • 2.4、為什么要設計雙親委派機制?
        • 2.5、全盤負責委托機制
        • 2.6、自定義類加載器示例
    • 3、tomcat怎么破解類加載機制
    • 4、記憶體模型
        • 4.1、執行緒私有區域
        • 4.2、執行緒共享區域
    • 5、物件的創建
        • 5.1、物件大小與指標壓縮
          • 5.1.1、物件大小
          • 5.1.2、什么是java物件的指標壓縮?
          • 5.1.3、為什么要進行指標壓縮?
    • 6、物件的分配程序
      • 6.1、堆疊上分配
      • 6.2、物件在Eden區分配(大部分情況,當 Eden 區沒有足夠空間進行分配時,出現Young GC)
      • 6.3、大物件直接進入老年代
      • 6.4、長期存活的物件將進入老年代
      • 6.5、物件動態年齡判斷
      • 6.6、老年代空間分配擔保機制)
    • 7、如何判斷一個類是無用的類
    • 8、finalize()方法最終判定物件是否存活
    • 9、常見參考型別(四大參考)
    • 10、物件回收
      • 10.1、參考計數法
      • 10.2、可達性分析演算法(gcroot)
    • 11、四大垃圾回收演算法
      • 11.1、分代收集理論
      • 11.2、標記-復制演算法
      • 11.3、標記-清除演算法
      • 11.4、標記-整理演算法
    • 12、常見oom
    • 13、垃圾收集器
      • 13.1、CMS(“標記-清除”演算法, -XX:+UseConcMarkSweepGC(old)
        • 13.1.1、運作程序(5大步驟)
        • 13.1.2、三色標記法
        • 13.1.3、concurrent model failure(浮動垃圾)
        • 13.1.4、background/foreground collector
        • 13.1.5、為什么G1用SATB?CMS用增量更新?
        • 13.1.6、漏標-讀寫屏障(解決方案)
          • 13.1.6.1、增量更新(Incremental Update)+寫屏障
          • 13.1.6.2、原始快照(Snapshot At The Beginning,SATB)+寫屏障
          • 13.1.6.3、并發標記時對漏標的處理方案
        • 13.1.7、promotion failed
        • 13.1.8、過早提升和提升失敗
        • 13.1.9、早提升的原因
        • 13.1.10、提升失敗原因
      • 13.2、G1
        • 13.2.1、運作流程
        • 13.2.2、Remembered Set(記錄集)/Card Table(卡表)
        • 13.2.3、Collect Set
        • 13.2.4、young gc的完整流程
        • 13.2.5、Mixed GC的完整流程
        • 13.2.6、Full GC
        • 13.2.7、Marking bitmaps/TAMS
        • 13.2.8、Pause Prediction Model
        • 13.2.9、G1收集器引數設定
        • 13.2.10、G1垃圾收集器優化建議( -XX:MaxGCPauseMills=50ms)
        • 13.2.11、什么場景適合使用G1
      • 13.3、ZGC
        • 13.3.1、主要目標
        • 13.3.2、color poin(顏色指標)
        • 13.3.3、運作程序
        • 13.3.4、存在的問題,怎么解決
        • 13.3.5、安全點與安全區域
        • 13.3.6、ZGC引數
        • 13.3.7、ZGC觸發時機
    • 14、如何選擇垃圾收集器
    • 15、各種命令(例如100%cpu的排查、死鎖的檢查)
      • 15.1、100%CPU的排查
      • 15.2、死鎖的檢查
    • 16、JIT(即時編譯器)
    • 17、逃逸分析

1、類加載機制

類加載程序分為 加載 >> 驗證 >> 準備 >> 決議 >> 初始化 >> 使用 >> 卸載 

1、加載
	在硬碟上查找并通過IO讀入位元組碼檔案,使用到類時才會加載,例如呼叫類的main()方法,new物件 等等,在加載階段會在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口 

2、驗證 
	校驗位元組碼檔案的正確性 
    
3、準備
	給類的靜態變數分配記憶體,并賦予默認值 

4、決議 
	將符號參考替換為直接參考,該階段會把一些靜態方法(符號參考,比如main()方法)替換為指向資料 所存記憶體的指標或句柄等(直接參考),這是所謂的靜態鏈接程序(類加載期間完成),動態鏈接是在程 序運行期間完成的將符號參考替換為直接參考,下節課會講到動態鏈接 

5、初始化 
	對類的靜態變數初始化為指定的值,執行靜態代碼塊

2、雙親委派機制(先找父親加載,不行再由兒子自己加載)

2.1、類加載器

1、根類加載器(**Bootstrap classLoader**):負責加載支撐JVM運行的位于JRE的lib目錄下的核心類別庫,比如rt.jar、charsets.jar等
    
2、擴展類加載器(**ExtClassLoader**):負責加載支撐JVM運行的位于JRE的lib目錄下的ext擴展目錄中的JAR類包
    
3、應用加載器(**AppClassLoader**):負責加載ClassPath路徑下的類包,主要就是加載你自己寫的那些類,負責加載用戶自定義路徑下的類包

2.2、加載器初始化程序

類運行加載全程序會創建JVM啟動器實體sun.misc.Launcher,sun.misc.Launcher初始化使用了單例模式設計,保證一個JVM虛擬機內只有一個sun.misc.Launcher實體,在Launcher構造方法內部,其創建了兩個類加載器,分別是sun.misc.Launcher.ExtClassLoader(擴展類加載器)和sun.misc.Launcher.AppClassLoader(應用類加載器),
    
JVM默認使用launcher的`getClassLoader()`方法回傳的類加載器`AppClassLoader`的實體來加載我們的應用程式,

2.3、雙親委派機制

應用程式類加載器AppClassLoader加載類的雙親委派機制原始碼,AppClassLoader的loadClass方法最侄訓呼叫其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:
首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接回傳,
如果此類沒有加載過,那么,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即呼叫parent.loadClass(name, false);).或者是呼叫bootstrap類加載器來加載,
如果父加載器及bootstrap類加載器都沒有找到指定的類,那么呼叫當前類加載器的findClass方法來完成類加載,

2.4、為什么要設計雙親委派機制?

1、沙箱安全機制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心API庫被隨意篡改
2、避免類的重復加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的唯一性

2.5、全盤負責委托機制

“全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及參考的類也由這個ClassLoder載入

2.6、自定義類加載器示例

自定義類加載器只需要繼承 java.lang.ClassLoader 類,該類有兩個核心方法,一個是loadClass(String, boolean),實作了雙親委派機制,還有一個方法是findClass,默認實作是空方法,所以我們自定義類加載器主要是重寫findClass方法,

3、tomcat怎么破解類加載機制

1、commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;

2、catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不可見;

3、sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對于所有Webapp可見,但是對于Tomcat容器不可見;

4、WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見,比如加載war包里相關的類, 每個war包應用都有自己的WebappClassLoader,實作相互隔離,比如不同war包應用引入了不同的spring版本,這樣實作就能加載各自的spring版本;

5、模擬實作Tomcat的JasperLoader熱加載

? 原理:后臺啟動執行緒監聽jsp檔案變化,如果變化了找到該jsp對應的servlet類的加載器參考 (gcroot),重新生成新的JasperLoader加載器賦值給參考,然后加載新的jsp對應的servlet類,之前的那個加載器因為沒有gcroot參考了,下一次gc的時候會被銷毀

=>總結:每個webappClassLoader加載自己的目錄下的class檔案,不會傳遞給父類加載器,打破了雙親委派機制,

4、記憶體模型

4.1、執行緒私有區域

程式計數器:是當前執行緒所執行的位元組碼的行號指示器,無OOM

虛擬機堆疊:是描述java方法執行的記憶體模型,每個方法在執行的同時都會創建一個堆疊幀(Stack Frame)用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等資訊,

  • 堆疊幀( Frame)是用來存盤資料和部分程序結果的資料結構,同時也被用來處理動態鏈接

(Dynamic Linking)、 方法回傳值和例外分派( Dispatch Exception),堆疊幀隨著方法呼叫而創

建,隨著方法結束而銷毀——無論方法是正常完成還是例外完成(拋出了在方法內未被捕獲的異

常)都算作方法結束,

本地方法堆疊:和 Java Stack 作用類似, 區別是虛擬機堆疊為執行 Java 方法服務, 而本地方法堆疊則為

Native 方法服務, 如果一個 VM 實作使用 C-linkage 模型來支持 Native 呼叫, 那么該堆疊將會是一個

C 堆疊,但 HotSpot VM 直接就把本地方法堆疊和虛擬機堆疊合二為一,

4.2、執行緒共享區域

==堆-運行時資料區:==是被執行緒共享的一塊記憶體區域,創建的物件和陣列都保存在 Java 堆記憶體中,也是垃圾收集器進行垃圾收集的最重要的記憶體區域,由于現代 VM 采用分代收集演算法, 因此 Java 堆從 GC 的角度還可以細分為: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年代

方法區/永久代(1.8之后元空間):用于存盤被 JVM 加載的類資訊**、常量靜態變數、**即時編譯器編譯后的代碼等資料. HotSpot VM把GC分代收集擴展至方法區, 即使用Java堆的永久代來實作方法區, 這樣 HotSpot 的垃圾收集器就可以像管理 Java 堆一樣管理這部分記憶體, 而不必為方法區開發專門的記憶體管理器(永久帶的記憶體回收的主要目標是針對常量池的回收和型別的卸載, 因此收益一般很小),

  • 運行時常量池(Runtime Constant Pool)是方法區的一部分,Class 檔案中除了有類的版本、欄位、方法、介面等描述等資訊外,還有一項資訊是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號參考,這部分內容將在類加載后存放到方法區的運行時常量池中,

在這里插入圖片描述

直接記憶體

jdk1.4后加入NIO(New Input/Output)類,引入了一種基于通道與緩沖區的I/O方式,它可以使用native函式庫直接分配堆外記憶體,然后通過一個存盤在java堆中的DirectByteBuffer物件作為這塊記憶體的參考進行操作,可以避免在Java堆和Native堆中來回復制資料

直接記憶體的分配不會受到Java堆大小的限制.避免大于物理記憶體的情況

5、物件的創建

在這里插入圖片描述

1、類加載檢查

虛擬機遇到一條new指令時,首先將去檢查這個指令的引數是否能在常量池中定位到一個類的符號參考,并且檢查這個符號參考代表的類是否已被加載、決議和初始化過,如果沒有,那必須先執行相應的類加載程序,
new指令對應到語言層面上講是,new關鍵詞、物件克隆、物件序列化等

2、分配記憶體

在類加載檢查通過后,接下來虛擬機將為新生物件分配記憶體,物件所需記憶體的大小在類 加載完成后便可完全確定,為物件分配空間的任務等同于把 一塊確定大小的記憶體從Java堆中劃分出來,
 //如何劃分記憶體?
 1、“指標碰撞”(Bump the Pointer)(默認用指標碰撞)
		如果Java堆中記憶體是絕對規整的,所有用過的記憶體都放在一邊,空閑的記憶體放在另一邊,中	  間放著一個指標作為分界點的指示器,那所分配記憶體就僅僅是把那個指標向空閑空間那邊挪動一段	 與物件大小相等的距離,
 2、“空閑串列”(Free List)
		如果Java堆中的記憶體并不是規整的,已使用的記憶體和空閑的記憶體相互交錯,那就沒有辦法簡	  單地進行指標碰撞了,虛擬機就必須維護一個串列,記錄上哪些記憶體塊是可用的,在分配的時候從	 串列中找到一塊足夠大的空間劃分給物件實體,并更新串列上的記錄,
 //解決并發問題的方法
 1、CAS(compare and swap)
		虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性來對分配記憶體空間的動作進行同	 步處理,
	2、本地執行緒分配緩沖(Thread Local Allocation Buffer,TLAB)
		把記憶體分配的動作按照執行緒劃分在不同的空間之中進行,即每個執行緒在Java堆中預先分配一	  小塊記憶體,通過-XX:+/-UseTLAB引數來設定虛擬機是否使用TLAB(JVM會默認開啟			-XX:+UseTLAB),-XX:TLABSize指定TLAB大小,

3.初始化

記憶體分配完成后,虛擬機需要將分配到的記憶體空間都初始化為零值(不包括物件頭), 如果使用TLAB,這一作業程序也可以提前至TLAB分配時進行,這一步操作保證了物件的實體欄位在Java代碼中可以不賦初始值就直接使用,程式能訪問到這些欄位的資料型別所對應的零值,

4.設定物件頭

初始化零值之后,虛擬機要對物件進行必要的設定,例如這個物件是哪個類的實體、如何才能找到類的元資料資訊、物件的哈希碼、物件的GC分代年齡等資訊,這些資訊存放在物件的物件頭Object Header之中,
在HotSpot虛擬機中,物件在記憶體中存盤的布局可以分為3塊區域:物件頭(Header)、 實體資料(Instance Data)和對齊填充(Padding), HotSpot虛擬機的物件頭包括兩部分資訊,第一部分用于存盤物件自身的運行時資料, 如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、執行緒持有的鎖、偏向執行緒ID、偏向時 間戳等,物件頭的另外一部分是型別指標,即物件指向它的類元資料的指標,虛擬機通過這個指標來確定這個物件是哪個類的實體,

在這里插入圖片描述

5.執行方法

執行<init>方法,即物件按照程式員的意愿進行初始化,對應到語言層面上講,就是為屬性賦值(注意,這與上面的賦零值不同,這是由程式員賦的值),和執行構造方法,

5.1、物件大小與指標壓縮

5.1.1、物件大小

物件大小可以用 jol--core 包查看

5.1.2、什么是java物件的指標壓縮?
  1. jdk1.6 update14開始,在64bit作業系統中,JVM支持指標壓縮
  2. jvm配置引數:UseCompressedOops,compressed--壓縮、oop(ordinary object pointer)--物件指標
  3. 啟用指標壓縮:-XX:+UseCompressedOops(默認開啟),禁止指標壓縮:-XX:-UseCompressedOops
5.1.3、為什么要進行指標壓縮?

1.在64位平臺的HotSpot中使用32位指標,記憶體使用會多出1.5倍左右,使用較大指標在主記憶體和快取之間移動資料,占用較大寬帶,同時GC也會承受較大壓力
2.為了減少64位平臺下記憶體的消耗,啟用指標壓縮功能
3.在jvm中,32位地址最大支持4G記憶體(2的32次方),可以通過對物件指標的壓縮編碼、解碼方式進行優化,使得jvm
只用32位地址就可以支持更大的記憶體配置(小于等于32G)
4.堆記憶體小于4G時,不需要啟用指標壓縮,jvm會直接去除高32位地址,即使用低虛擬地址空間
5.堆記憶體大于32G時,壓縮指標會失效,會強制使用64位(即8位元組)來對java物件尋址,這就會出現1的問題,所以堆記憶體不要大于32G為好

6、物件的分配程序

在這里插入圖片描述

6.1、堆疊上分配

我們通過JVM記憶體分配可以知道JAVA中的物件都是在堆上進行分配,當物件沒有被參考的時候,需要依靠GC進行回收記憶體,如果物件數量較多的時候,會給GC帶來較大壓力,也間接影響了應用的性能,為了減少臨時物件在堆內分配的數量,JVM通過逃逸分析確定該物件不會被外部訪問,如果不會逃逸可以將該物件在堆疊上分配記憶體,這樣該物件所占用的記憶體空間就可以隨堆疊幀出堆疊而銷毀,就減輕了垃圾回收的壓力,

==物件逃逸分析:==就是分析物件動態作用域,當一個物件在方法中被定義后,它可能被外部方法所參考,例如作為呼叫引數傳遞到其他地方中,

public User test1() {
  User user = new User();
  user.setId(1);
  user.setName("zhuge");
  //TODO 保存到資料庫
  return user;
 }

 public void test2() {
  User user = new User();
  user.setId(1);
  user.setName("zhuge");
  //TODO 保存到資料庫
 }

很顯然test1方法中的user物件被回傳了,這個物件的作用域范圍不確定,test2方法中的user物件我們可以確定當方法結束這個物件就可以認為是無效物件了,對于這樣的物件我們其實可以將其分配在堆疊記憶體里,讓其在方法結束時跟隨堆疊記憶體一起被回收掉,
JVM對于這種情況可以通過開啟逃逸分析引數(-XX:+DoEscapeAnalysis)來優化物件記憶體分配位置,使其通過標量替換優先分配在堆疊上(堆疊上分配),JDK7之后默認開啟逃逸分析,如果要關閉使用引數(-XX:-DoEscapeAnalysis)
==標量替換:==通過逃逸分析確定該物件不會被外部訪問,并且物件可以被進一步分解時,JVM不會創建該物件,而是將該物件成員變數分解若干個被這個方法使用的成員變數所代替,這些代替的成員變數在堆疊幀或暫存器上分配空間,這樣就不會因為沒有一大塊連續空間導致物件記憶體不夠分配,開啟標量替換引數(-XX:+EliminateAllocations),JDK7之后默認開啟,
==標量與聚合量:==標量即不可被進一步分解的量,而JAVA的基本資料型別就是標量(如:int,long等基本資料型別以及reference型別等),標量的對立就是可以被進一步分解的量,而這種量稱之為聚合量,而在JAVA中物件就是可以被進一步分解的聚合量,

結論:堆疊上分配依賴于逃逸分析和標量替換

6.2、物件在Eden區分配(大部分情況,當 Eden 區沒有足夠空間進行分配時,出現Young GC)

大量的物件被分配在eden區,eden區滿了后會觸發Young GC,可能會有99%以上的物件成為垃圾被回收掉,剩余存活的物件會被挪到s0區,下一次eden區滿了后又會觸發Young GC,把eden區和s0區垃圾物件回收,把剩余存活的物件一次性挪動到另外一塊為空的s1區,因為新生代的物件都是朝生夕死的,存活時間很短,所以JVM默認的8:1:1的比例是很合適的,讓eden區盡量的大,survivor區夠用即可,JVM默認有這個引數-XX:+UseAdaptiveSizePolicy(默認開啟),會導致這個8:1:1比例自動變化,如果不想這個比例有變化可以設定引數-XX:-UseAdaptiveSizePolicy

6.3、大物件直接進入老年代

大物件就是需要大量連續記憶體空間的物件(比如:字串、陣列),JVM引數 -XX:PretenureSizeThreshold 可以設定大物件的大小,如果物件超過設定大小會直接進入老年代,不會進入年輕代,這個引數只在 Serial 和ParNew兩個收集器下有效,
比如設定JVM引數:-XX:PretenureSizeThreshold=1000000 (單位是位元組) -XX:+UseSerialGC ,再執行下上面的第一個程式會發現大物件直接進了老年代

為什么要這樣呢?
為了避免為大物件分配記憶體時的復制操作而降低效率,

6.4、長期存活的物件將進入老年代

虛擬機給每個物件一個物件年齡(Age)計數器,如果物件在 Eden 出生并經過第一次 Minor GC 后仍然能夠存活,并且能被 Survivor 容納的話,將被移動到 Survivor
空間中,并將物件年齡設為1,物件在 Survivor 中每熬過一次 MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲,CMS收集器默認6歲,不同的垃圾收集器會略微有點不同),就會被晉升到老年代中,物件晉升到老年代的年齡閾值,可以通過引數 -XX:MaxTenuringThreshold 來設定,

6.5、物件動態年齡判斷

當前放物件的Survivor區域里(其中一塊區域,放物件的那塊s區),一批物件的總大小大于這塊Survivor區域記憶體大小的50%(-XX:TargetSurvivorRatio可以指定),那么此時大于等于這批物件年齡最大值的物件,就可以直接進入老年代了,例如Survivor區域里現在有一批物件,年齡1+年齡2+年齡n的多個年齡物件總和超過了Survivor區域的50%,此時就會把年齡n(含)以上的物件都放入老年代,這個規則其實是希望那些可能是長期存活的物件,盡早進入老年代,物件動態年齡判斷機制一般是在young gc之后觸發的,

6.6、老年代空間分配擔保機制)

年輕代每次minor gc之前JVM都會計算下老年代剩余可用空間
如果這個可用空間小于年輕代里現有的所有物件大小之和(包括垃圾物件)
就會看一個“-XX:-HandlePromotionFailure”(jdk1.8默認就設定了) 的引數是否設定了
如果有這個引數,就會看看老年代的可用記憶體大小,是否大于之前每一次minor gc后進入老年代的物件的平均大小,
如果上一步結果是小于或者之前說的引數沒有設定,那么就會觸發一次Full gc,對老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的物件就會發生"OOM",

當然,如果minor gc之后剩余存活的需要挪動到老年代的物件大小還是大于老年代可用空間,那么也會觸發full gc,full gc完之后如果還是沒有空間放minor gc之后的存活物件,則也會發生“OOM”

在這里插入圖片描述

7、如何判斷一個類是無用的類

  • 該類所有的實體都已經被回收,也就是 Java 堆中不存在該類的任何實體,
  • 加載該類的 ClassLoader 已經被回收,
  • 該類對應的 java.lang.Class 物件沒有在任何地方被參考,無法在任何地方通過反射訪問該類的方法,

8、finalize()方法最終判定物件是否存活

1. 第一次標記并進行一次篩選,

篩選的條件是此物件是否有必要執行finalize()方法,

當物件沒有覆寫finalize方法,物件將直接被回收,

2. 第二次標記

如果這個物件覆寫了finalize方法,finalize方法是物件脫逃死亡命運的最后一次機會,如果物件要在finalize()中成功拯救 自己,只要重新與參考鏈上的任何的一個物件建立關聯即可,譬如把自己賦值給某個類變數或物件的成員變數,那在第 二次標記時它將移除出“即將回收”的集合,如果物件這時候還沒逃脫,那基本上它就真的被回收了,

注意:一個物件的finalize()方法只會被執行一次,也就是說通過呼叫finalize方法自我救命的機會就一次,

9、常見參考型別(四大參考)

1、強參考:普通的變數參考

2、軟參考(SoftReference):將物件用SoftReference軟參考型別的物件包裹,正常情況不會被回收,但是GC做完后發現釋放不出空間存放新的物件,則會把這些軟參考的物件回收掉,軟參考可用來實作記憶體敏感的高速快取,

  • 使用場景:瀏覽器的后退按鈕

3、弱參考(WeakReference):將物件用WeakReference軟參考型別的物件包裹,弱參考跟沒參考差不多,GC會直接回收掉,很少用

4、虛參考:虛參考也稱為幽靈參考或者幻影參考,它是最弱的一種參考關系,幾乎不用

10、物件回收

什么叫物件回收?

堆中幾乎放著所有的物件實體,對堆垃圾回收前的第一步就是要判斷哪些物件已經死亡(即不能再被任何途徑使用的物件),

10.1、參考計數法

給物件中添加一個參考計數器,每當有一個地方參考它,計數器就加1;當參考失效,計數器就減1;任何時候計數器為0的物件就是不可能再被使用的,
缺點:回圈參考問題

10.2、可達性分析演算法(gcroot)

將“GC Roots” 物件作為起點,從這些節點開始向下搜索參考的物件,找到的物件都標記為非垃圾物件,其余未標記的物件都是垃圾物件
GC Roots根節點:執行緒堆疊的本地變數、靜態變數、本地方法堆疊的變數等等

在這里插入圖片描述

11、四大垃圾回收演算法

11.1、分代收集理論

當前虛擬機的垃圾收集都采用分代收集演算法,這種演算法沒有什么新的思想,只是根據物件存活周期的不同將記憶體分為幾塊,一般將java堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集演算法,
比如在新生代中,每次收集都會有大量物件(近99%)死去,所以可以選擇復制演算法,只需要付出少量物件的復制成本就可以完成每次垃圾收集,而老年代的物件存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記-清除”或“標記-整理”演算法進行垃圾收集,注意,“標記-清除”或“標記-整理”演算法會比復制演算法慢10倍以上,

11.2、標記-復制演算法

它可以將記憶體分為大小相同的兩塊,每次使用其中的一塊,當這一塊的
記憶體使用完后,就將還存活的物件復制到另一塊去,然后再把使用的空間一次清理掉,這樣就使每次的記憶體回收都是對記憶體區間的一半進行回收,

在這里插入圖片描述

11.3、標記-清除演算法

演算法分為“標記”和“清除”階段:標記存活的物件, 統一回收所有未被標記的物件(一般選擇這種);也可以反過來,標記出所有需要回收的物件,在標記完成后統一回收所有被標記的物件 ,它是最基礎的收集演算法,比較簡單,但是會帶來兩個明顯的問題:

  1. 效率問題 (如果需要標記的物件太多,效率不高)
  2. 空間問題(標記清除后會產生大量不連續的碎片)

在這里插入圖片描述

11.4、標記-整理演算法

根據老年代的特點特出的一種標記演算法,標記程序仍然與“標記-清除”演算法一樣,但后續步驟不是直接對可回收物件回收,而是讓所有存活的物件向一端移動,然后直接清理掉端邊界以外的記憶體,

在這里插入圖片描述

12、常見oom

1、java.lang.StackOverflowError:

? 報這個錯誤一般是由于方法深層次的呼叫,默認的執行緒堆疊空間大小一般與具體的硬體平臺有關,堆疊記憶體為執行緒私有的空間,每個執行緒都會創建私有的堆疊記憶體,堆疊空間記憶體設定過大,創建執行緒數量較多時會出現堆疊記憶體溢位StackOverflowError,同時,堆疊記憶體也決定方法呼叫的深度,堆疊記憶體過小則會導致方法呼叫的深度較小,如遞回呼叫的次數較少,

2、java.lang.OutOfMemoryError: Java heap space

? Heap size 設定 JVM堆的設定是指:java程式執行程序中JVM能夠調配使用的記憶體空間的設定,JVM在啟動的時候會自己主動設定Heap size的值,其初始空間(即-Xms)是物理記憶體的1/64最大空間(-Xmx)是物理記憶體的1/4,能夠利用JVM提供的-Xmn -Xms -Xmx等選項可進行設定,Heap size 的大小是Young Generation 和Tenured Generaion 之和,

3、java.lang.OutOfMemoryError:GC overhead limit exceeded

? GC回收時間過長時會拋出的OutOfMemory,過長是指,超過98%的時間都在用來做GC并且回收了不到2%的堆記憶體,連續多次的GC,都回收了不到2%的極端情況下才會拋出,假如不拋出GC overhead limit 錯誤會發生什么事情呢?那就是GC清理出來的一點記憶體很快又會被再次填滿,強迫GC再次執行,這樣造成惡性回圈,CPU的使用率一直很高,但是GC沒有任何的進展,

4、java.lang.OutOfMemoryError:Direct buffer memory

? 寫NIO程式經常使用到ByteBuffer來讀取或者寫入資料,這是一種基于通道與緩沖區的I/O方式,它可以使用Native函式庫直接分配堆外記憶體,然后通過一個存盤在java堆里面的DirectByteBuffer物件作為這塊記憶體的參考進行操作,這樣能在一些場景中提高性能,因為避免了java堆和Native堆中來回復制資料,

  • ByteBuffer.allocate(capability) :這種方式是分配JVM堆記憶體,屬于GC管轄范圍之內,由于需要拷貝,所以速度相對較慢;
  • ByteBuffer.allocateDirect(capability):這種方式是直接分配OS本地記憶體,不屬于GC管轄范圍之內,由于不需要記憶體拷貝所以速度相對較快,

但是如果不斷分配本地記憶體,堆記憶體很少使用,那么JVM就不需要執行GC,DirectByteBuffer物件就不會被回收,這時候堆記憶體充足,但是本地記憶體已經用光了,再次嘗試分配的時候就會出現OutOfMemoryError,那么程式就直接崩潰了,

5、java.lang.OutOfMemoryError:unable to create new native thread

? 準確的說,這一個例外是和程式運行的平臺相關的,導致的原因:

  • 創建了太多的執行緒,一個應用創建多個執行緒,超過系統承載極限;
  • 服務器不允許應用程式創建這么多的執行緒,Linux系統默認的允許單個行程可以創建的執行緒數量是1024個,當創建多 執行緒數量多于這個數字的時候就會拋出此例外

如何解決呢?

  • 想辦法減少應用程式創建的執行緒的數量,分析應用是否真的需要創建這么多的執行緒,如果不是,改變代碼將執行緒數量降到最低;
  • 對于有的應用,確實需要創建很多的執行緒,遠超過Linux限制的1024個 限制,那么可以通過修改Linux服務器的配置,擴大Linux的默認限制,

6、java.lang.OutOfMemoryError:MetaSpace

? 元空間的本質和永久代類似,都是對JVM規范中的方法區的實作,不過元空間與永久代之間最大的區別在于:元空間不在虛擬機中,而是使用的本地記憶體,因此,默認情況下,元空間的大小僅僅受到本地記憶體的限制 ,

元空間存放了以下的內容:

  • 虛擬機加載的類資訊;
  • 常量池;
  • 靜態變數;
  • 即時編譯后的代碼

模擬MetaSpace空間溢位,我們不斷生成類往元空間里灌,類占據的空間總是會超過MetaSpace指定的空間大小的

查看元空間的大小:java -XX:+PrintFlagsInitial

13、垃圾收集器

13.1、CMS(“標記-清除”演算法, -XX:+UseConcMarkSweepGC(old)

定義:以獲取最短回收停頓時間為目標的收集器

13.1.1、運作程序(5大步驟)

1、初始標記: 暫停所有的其他執行緒(STW),并記錄下gc roots直接能參考的物件,速度很快,
2、并發標記: 并發標記階段就是從GC Roots的直接關聯物件開始遍歷整個物件圖的程序, 這個程序耗時較長但是不需要停頓用戶執行緒, 可以與垃圾收集執行緒一起并發運行,因為用戶程式繼續運行,可能會有導致已經標記過的物件狀態發生改變,
3、重新標記: 重新標記階段就是為了修正并發標記期間因為用戶程式繼續運行而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比并發標記階段時間短,主要用到增量更新演算法做重新標記,
4、并發清理: 開啟用戶執行緒,同時GC執行緒開始對未標記的區域做清掃,這個階段如果有新增物件會被標記為三色標記法里面的黑色不做任何處理
5、并發重置:重置本次GC程序中的標記資料,

在這里插入圖片描述

主要優點:并發收集、低停頓,但是它有下面幾個明顯的缺點:

  1. 對CPU資源敏感(會和服務搶資源);
  2. 無法處理浮動垃圾(在并發標記和并發清理階段又產生垃圾,這種浮動垃圾只能等到下一次gc再清理了);
  3. 它使用的回收演算法-“標記-清除”演算法會導致收集結束時會有大量空間碎片產生,當然通過引數==-XX:+UseCMSCompactAtFullCollection可以讓jvm在執行完標記清除后再做整理==
  4. 執行程序中的不確定性,會存在上一次垃圾回識訓沒執行完,然后垃圾回收又被觸發的情況,特別是在并發標記和并發清理階段會出現,一邊回收,系統一邊運行,也許沒回收完就再次觸發full gc,也就是"concurrent mode failure",此時會進入stop the world,用serial old垃圾收集器來回收

CMS的相關核心引數

  1. -XX:+UseConcMarkSweepGC:啟用cms
  2. -XX:ConcGCThreads:并發的GC執行緒數
  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做壓縮整理(減少碎片)
  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后壓縮一次,默認是0,代表每次FullGC后都會壓縮一次
  5. -XX:CMSInitiatingOccupancyFraction: 當老年代使用達到該比例時會觸發FullGC(默認是92,這是百分比)
  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用設定的回收閾值(-XX:CMSInitiatingOccupancyFraction設定的值),如果不指定,JVM僅在第一次使用設定值,后續則會自動調整
  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前啟動一次minor gc,目的在于減少老年代對年輕代的參考,降低CMS GC的標記階段時的開銷,一般CMS的GC耗時 80%都在標記階段
  8. -XX:+CMSParallellnitialMarkEnabled:表示在初始標記的時候多執行緒執行,縮短STW
  9. -XX:+CMSParallelRemarkEnabled:在重新標記的時候多執行緒執行,縮短STW;

13.1.2、三色標記法

黑色: 表示物件已經被垃圾收集器訪問過, 且這個物件的所有參考都已經掃描過, 黑色的物件代表已經掃描過, 它是安全存活的, 如果有其他物件參考指向了黑色物件, 無須重新掃描一遍, 黑色物件不可能直接(不經過灰色物件) 指向某個白色物件,

灰色: 表示物件已經被垃圾收集器訪問過, 但這個物件上至少存在一個參考還沒有被掃描過,

白色: 表示物件尚未被垃圾收集器訪問過, 顯然在可達性分析剛剛開始的階段, 所有的物件都是白色的, 若在分析結束的階段, 仍然是白色的物件, 即代表不可達,

在這里插入圖片描述

13.1.3、concurrent model failure(浮動垃圾)

在并發標記程序中,如果由于方法運行結束導致部磁區域變數(gcroot)被銷毀,這個gcroot參考的物件之前又被掃描過(被標記為非垃圾物件),那么本輪GC不會回收這部分記憶體,這部分本應該回收但是沒有回收到的記憶體,被稱之為“浮動垃圾”,浮動垃圾并不會影響垃圾回收的正確性,只是需要等到下一輪垃圾回收中才被清除,另外,針對并發標記(還有并發清理)開始后產生的新物件,通常的做法是直接全部當成黑色,本輪不會進行清除,這部分物件期間可能也會變為垃圾,這也算是浮動垃圾的一部分,

13.1.4、background/foreground collector

-XX:ConcGCThreads=4和-XX:+ExplicitGCInvokesConcurrent開啟foreground CMS GC,CMS gc 有兩種模式,background和foreground,正常的cms gc使用background模式,就是我們平時說的cms gc;當并發收集失敗或者呼叫了System.gc()的時候,就會導致一次full gc,這個fullgc是不是cms回收,而是Serial單執行緒回收器,加入了引數 -XX:ConcGCThreads=4 后,執行full gc的時候,就變成了CMS foreground gc,它是并行full gc,只會執行cms中stop the world階段的操作,效率比單執行緒Serial full GC要高;需要注意的是它只會回收old,因為cms收集器是老年代收集器;而正常的Serial收集是包含整個堆的,加入了引數==-XX:+ExplicitGCInvokesConcurrent==,代表永久帶也會被cms收集;

13.1.5、為什么G1用SATB?CMS用增量更新?

SATB相對增量更新效率會高(當然SATB可能造成更多的浮動垃圾),因為不需要在重新標記階段再次深度掃描被洗掉參考物件,而CMS對增量參考的根物件會做深度掃描,G1因為很多物件都位于不同的region,CMS就一塊老年代區域,重新深度掃描物件的話G1的代價會比CMS高,所以G1選擇SATB不深度掃描物件,只是簡單標記,等到下一輪GC再深度掃描,

13.1.6、漏標-讀寫屏障(解決方案)

13.1.6.1、增量更新(Incremental Update)+寫屏障

增量更新就是當黑色物件插入新的指向白色物件的參考關系時, 就將這個新插入的參考記錄下來, 等并發掃描結束之后, 再將這些記錄過的參考關系中的黑色物件為根, 重新掃描一次, 這可以簡化理解為, 黑色物件一旦新插入了指向白色物件的參考之后, 它就變回灰色物件了,

  • 寫屏障實作增量更新

當物件A的成員變數的參考發生變化時,比如新增參考(a.d = d),我們可以利用寫屏障,將A新的成員變數參考物件D
記錄下來:
void post_write_barrier(oop* field, oop new_value) {
remark_set.add(new_value); // 記錄新參考的物件
}

13.1.6.2、原始快照(Snapshot At The Beginning,SATB)+寫屏障

原始快照就是當灰色物件要洗掉指向白色物件的參考關系時, 就將這個要洗掉的參考記錄下來, 在并發掃描結束之后,再將這些記錄過的參考關系中的灰色物件為根, 重新掃描一次,這樣就能掃描到白色的物件,將白色物件直接標記為黑色(目的就是讓這種物件在本輪gc清理中能存活下來,待下一輪gc的時候重新掃描,這個物件也有可能是浮動垃圾)

以上無論是對參考關系記錄的插入還是洗掉, 虛擬機的記錄操作都是通過寫屏障實作的,

  • 寫屏障實作SATB

    當物件B的成員變數的參考發生變化時,比如參考消失(a.b.d = null),我們可以利用寫屏障,將B原來成員變數的參考
    物件D記錄下來:
    void pre_write_barrier(oop* field) {
    oop old_value = *field; // 獲取舊值
    remark_set.add(old_value); // 記錄原來的參考物件
    }

13.1.6.3、并發標記時對漏標的處理方案

CMS:寫屏障 + 增量更新
G1,Shenandoah:寫屏障 + SATB
ZGC:讀屏障

工程實作中,讀寫屏障還有其他功能,比如寫屏障可以用于記錄跨代/區參考的變化,讀屏障可以用于支持移動物件的并發執行等,功能之外,還有性能的考慮,所以對于選擇哪種,每款垃圾回收器都有自己的想法,

13.1.7、promotion failed

這個例外發生在年輕帶回收的時候;
在進行Minor GC時,Survivor Space放不下,物件只能放入老年代,而此時老年代也放不下造成的,多數是由于老年帶有足夠的空閑空間,但是由于碎片較多,新生代要轉移到老年帶的物件比較大,找不到一段連續區域存放這個物件導致的,

13.1.8、過早提升和提升失敗

在 Minor GC 程序中,Survivor Unused 可能不足以容納 Eden 和另一個 Survivor 中的存活物件, 那么多余的將被移到老年代, 稱為過早提升(Premature Promotion),這會導致老年代中短期存活物件的增長, 可能會引發嚴重的性能問題, 再進一步, 如果老年代滿了, Minor GC 后會進行 Full GC, 這將導致遍歷整個堆, 稱為提升失敗(Promotion Failure)

13.1.9、早提升的原因

  1. Survivor空間太小,容納不下全部的運行時短生命周期的物件,如果是這個原因,可以嘗試將Survivor調大,否則端生命周期的物件提升過快,導致老年代很快就被占滿,從而引起頻繁的full gc;
  2. 物件太大,Survivor和Eden沒有足夠大的空間來存放這些大象;

13.1.10、提升失敗原因

當提升的時候,發現老年代也沒有足夠的連續空間來容納該物件,
為什么是沒有足夠的連續空間而不是空閑空間呢?
老年代容納不下提升的物件有兩種情況:

  1. 老年代空閑空間不夠用了;

  2. 老年代雖然空閑空間很多,但是碎片太多,沒有連續的空閑空間存放該物件;

    解決方法

    1. 如果是因為記憶體碎片導致的大物件提升失敗,cms需要進行空間整理壓縮;
    2. 如果是因為提升過快導致的,說明Survivor 空閑空間不足,那么可以嘗試調大 Survivor;
    3. 如果是因為老年代空間不夠導致的,嘗試將CMS觸發的閾值調低

13.2、G1

定義:面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量記憶體的機器. 以極高概率滿足GC,停頓時間要求的同時,還具備高吞吐量性能特征

13.2.1、運作流程

G1將Java堆劃分為多個大小相等的獨立區域(Region),JVM最多可以有2048個Region,
一般Region大小等于堆大小除以2048,比如堆大小為4096M,則Region大小為2M,當然也可以用引數"-XX:G1HeapRegionSize"手動指定Region大小,但是推薦默認的計算方式,
G1保留了年輕代和老年代的概念,但不再是物理隔閡了,它們都是(可以不連續)Region的集合,
默認年輕代對堆記憶體的占比是5%,如果堆大小為4096M,那么年輕代占據200MB左右的記憶體,對應大概是100個Region,可以通過“-XX:G1NewSizePercent”設定新生代初始占比,在系統運行中,JVM會不停的給年輕代增加更多的Region,但是最多新生代的占比不會超過60%,可以通過“-XX:G1MaxNewSizePercent”調整,年輕代中的Eden和Survivor對應的region也跟之前一樣,默認8:1:1,假設年輕代現在有1000個region,eden區對應800個,s0對應100個,s1對應100個,
一個Region可能之前是年輕代,如果Region進行了垃圾回收,之后可能又會變成老年代,也就是說Region的區域功能
可能會動態變化,G1垃圾收集器對于物件什么時候會轉移到老年代跟之前講過的原則一樣,唯一不同的是對大物件的處理,G1有專門分配大物件的Region叫Humongous區,而不是讓大物件直接進入老年代的Region中,在G1中,大物件的判定規則就是一個大物件超過了一個Region大小的50%,比如按照上面算的,每個Region是2M,只要一個大物件超過了1M,就會被放入Humongous中,而且一個大物件如果太大,可能會橫跨多個Region來存放,

Humongous區專門存放短期巨型物件,不用直接進老年代,可以節約老年代的空間,避免因為老年代空間不夠的GC開銷,
Full GC的時候除了收集年輕代和老年代之外,也會將Humongous區一并回收,

G1收集器一次GC的運作程序大致分為以下4個步驟:

  • 初始標記(initial mark,STW):暫停所有的其他執行緒,并記錄下gc roots直接能參考的物件,速度很快 ;

  • 并發標記(Concurrent Marking):同CMS的并發標記

  • 最終標記(Remark,STW):同CMS的重新標記

  • 篩選回收(Cleanup,STW):篩選回收階段首先對各個Region的==回收價值和成本進行排序,根據用戶所期望的GC停頓時間(可以用JVM引數 -XX:MaxGCPauseMillis指定)來制定回收計劃,==比如說老年代此時有1000個Region都滿了,但是因為根據預期停頓時間,本次垃圾回收可能只能停頓200毫秒,那么通過之前回收成本計算得知,可能回收其中800個Region剛好需要200ms,那么就只會回收800個Region(Collection Set,要回收的集合),盡量把GC導致的停頓時間控制在我們指定的范圍內,這個階段其實也可以做到與用戶程式一起并發執行,但是因為只回收一部分Region,時間是用戶可控制的,而且停頓用戶執行緒將大幅提高收集效率,不管是年輕代或是老年代,回收演算法主要用的是復制演算法,將一個region中的存活物件復制到另一個region中,這種不會像CMS那樣回收完因為有很多記憶體碎片還需要整理一次,G1采用復制演算法回收幾乎不會有太多記憶體碎片,(注意:CMS回收階段是跟用戶執行緒一起并發執行的,G1因為內部實作太復雜暫時沒實作并發回收,不過到了Shenandoah就實作了并發收集,Shenandoah可以看成是G1的升級版本)

在這里插入圖片描述

==G1收集器在后臺維護了一個優先串列,每次根據允許的收集時間,優先選擇回收價值最大的Region(這也就是它的名字Garbage-First的由來),比如一個Region花200ms能回收10M垃圾,另外一個Region花50ms能回收20M垃圾,在回收時間有限情況下,G1當然會優先選擇后面這個Region回收,==這種使用Region劃分記憶體空間以及有優先級的區域回收方式,保證了G1收集器在有限時間內可以盡可能高的收集效率,

被視為JDK1.7以上版本Java虛擬機的一個重要進化特征,它具備以下特點

  • 并行與并發:G1能充分利用CPU、多核環境下的硬體優勢,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓時間,部分其他收集器原本需要停頓Java執行緒來執行GC動作,G1收集器仍然可以通過并發的方式讓java程式繼續執行,
  • 分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念,
  • 空間整合:與CMS的“標記–清理”演算法不同,G1從整體來看是基于“標記整理”演算法實作的收集器;從區域上來看是基于“復制”演算法實作的,
    可預測的停頓:這是G1相對于CMS的另一個大優勢,降低停頓時間是G1 和 CMS 共同的關注點,但G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段(通過引數"-XX:MaxGCPauseMillis"指定)內完成垃圾收集,

毫無疑問, 可以由用戶指定期望的停頓時間是G1收集器很強大的一個功能, 設定不同的期望停頓時間, 可使得G1在不同應用場景中取得關注吞吐量和關注延遲之間的最佳平衡, 不過, 這里設定的“期望值”必須是符合實際的, 不能異想天開, 畢竟G1是要凍結用戶執行緒來復制物件的, 這個停頓時間再怎么低也得有個限度, 它默認的停頓目標為兩百毫秒, 一般來說, 回收階段占到幾十到一百甚至接近兩百毫秒都很正常, 但如果我們把停頓時間調得非常低, 譬如設定為二十毫秒, 很可能出現的結果就是由于停頓目標時間太短, 導致每次選出來的回收集只占堆記憶體很小的一部分, 收集器收集的速度逐漸跟不上分配器分配的速度, 導致垃圾慢慢堆積, 很可能一開始收集器還能從空閑的堆記憶體中獲得一些喘息的時間, 但應用運行時間一長就不行了, 最終占滿堆引發Full GC反而降低性能, 所以通常把期望停頓時間設定為一兩百毫秒或者兩三百毫秒會是比較合理的,

在這里插入圖片描述

13.2.2、Remembered Set(記錄集)/Card Table(卡表)

在新生代做GCRoots可達性掃描程序中可能會碰到跨代參考的物件,這種如果又去對老年代再去掃描效率太低了,
為此,在新生代可以引入記錄集(Remember Set)的資料結構(記錄從非收集區到收集區的指標集合),避免把整個老年代加入GCRoots掃描范圍,事實上并不只是新生代、 老年代之間才有跨代參考的問題, 所有涉及部磁區域收集(Partial GC) 行為的垃圾收集器, 典型的如G1、 ZGC和Shenandoah收集器, 都會面臨相同的問題,
垃圾收集場景中,收集器只需通過記憶集判斷出某一塊非收集區域是否存在指向收集區域的指標即可,無需了解跨代參考指標的全部細節,
hotspot使用一種叫做“卡表”(cardtable)的方式實作記憶集,也是目前最常用的一種方式,關于卡表與記憶集的關系,可以類比為Java語言中HashMap與Map的關系
卡表是使用一個位元組陣列實作:CARD_TABLE[ ]每個元素對應著其標識的記憶體區域一塊特定大小的記憶體塊,稱為“卡頁”,
HotSpot使用的卡頁是2^9大小,即512位元組,

一個卡頁中可包含多個物件,只要有一個物件的欄位存在跨代指標,其對應的卡表的元素標識就變成1,表示該元素變臟,否則為0,
GC時,只要篩選本收集區的卡表中變臟的元素加入GCRoots里,

卡表如何維護?

卡表變臟上面已經說了,但是需要知道如何讓卡表變臟,即發生參考欄位賦值時,如何更新卡表對應的標識為1,Hotspot使用寫屏障維護卡表狀態,

13.2.3、Collect Set

Collect Set(CSet)是指,在Evacuation階段,由G1垃圾回收器選擇的待回收的Region集合,G1垃圾回收器的軟實時的特性就是通過CSet的選擇來實作的,對應于演算法的兩種模式fully-young generational mode和partially-young mode,CSet的選擇可以分成兩種:

  1. 在fully-young generational mode下:顧名思義,該模式下CSet將只包含young的Region,G1將調整young的Region的數量來匹配軟實時的目標;
  2. 在partially-young mode下:該模式會選擇所有的young region,并且選擇一部分的old region,old region的選擇將依據在Marking cycle phase中對存活物件的計數,G1選擇存活物件最少的Region進行回收,

13.2.4、young gc的完整流程

YoungGC并不是說現有的Eden區放滿了就會馬上觸發,G1會計算下現在Eden區回收大概要多久時間,如果回收時間遠遠小于引數 -XX:MaxGCPauseMills 設定的值,那么增加年輕代的region,繼續給新物件存放,不會馬上做Young GC,直到下一次Eden區放滿,G1計算回收時間接近引數 -XX:MaxGCPauseMills 設定的值,那么就會觸發Young GC

13.2.5、Mixed GC的完整流程

不是FullGC,老年代的堆占有率達到引數(-XX:InitiatingHeapOccupancyPercent)設定的值則觸發,回收所有的Young和部分Old(根據期望的GC停頓時間確定old區垃圾收集的優先順序)以及大物件區,正常情況G1的垃圾收集是先做MixedGC,主要使用復制演算法,需要把各個region中存活的物件拷貝到別的region里去,拷貝程序中如果發現沒有足夠的空region能夠承載拷貝物件就會觸發一次Full GC

13.2.6、Full GC

停止系統程式,然后采用單執行緒進行標記、清理和壓縮整理,好空閑出來一批Region來供下一次MixedGC使用,這個程序是非常耗時的,(Shenandoah優化成多執行緒收集了)

13.2.7、Marking bitmaps/TAMS

Marking bitmap是一種資料結構,其中的每一個bit代表的是一個可用于分配給物件的起始地址,舉例來說:

img

bitmap

其中addrN代表的是一個物件的起始地址,綠色的塊代表的是在該起始地址處的物件是存活物件,而其余白色的塊則代表了垃圾物件,
G1使用了兩個bitmap,一個叫做previous bitmap,另外一個叫做next bitmap,previous bitmap記錄的是上一次的標記階段完成之后的構造的bitmap;next bitmap則是當前正在標記階段正在構造的bitmap,在當前標記階段結束之后,當前標記的next bitmap就變成了下一次標記階段的previous bitmap,

TAMS(top at mark start)變數,是一對用于區分在標記階段新分配物件的變數,分別被稱為previous TAMS和next TAMS,在previous TAMS和next TAMS之間的物件則是本次標記階段時候新分配的物件,如圖:

img

previous TMAS 和 next TAMS

白色region代表的是空閑空間,綠色region代表是存活物件,橙色region代表的在此次標記階段新分配的物件,注意的是,在橙色區域的物件,并不能確保它們都事實上是存活的,

13.2.8、Pause Prediction Model

停頓預測模型,通過用戶設定的GC停頓時間(引數-XX:MaxGCPauseMillis),G1以衰減平均值為理論基礎,計算需要回收的Region數量從而進行滿足,

13.2.9、G1收集器引數設定

	-XX:+UseG1GC:使用G1收集器

    -XX:ParallelGCThreads:指定GC作業的執行緒數量

    -XX:G1HeapRegionSize:指定磁區大小(1MB~32MB,且必須是2的N次冪),默認將整堆劃分為2048個磁區

    -XX:MaxGCPauseMillis:目標暫停時間(默認200ms)

    -XX:G1NewSizePercent:新生代記憶體初始空間(默認整堆5%)

    -XX:G1MaxNewSizePercent:新生代記憶體最大空間

    -XX:TargetSurvivorRatio:Survivor區的填充容量(默認50%),Survivor區域里的一批物件(年齡1+年齡2+年齡n的多個年齡物件)總和超過了Survivor區域的50%,此時就會把年齡n()以上的物件都放入老年代

    -XX:MaxTenuringThreshold:最大年齡閾值(默認15)

    -XX:InitiatingHeapOccupancyPercent:老年代占用空間達到整堆記憶體閾值(默認45%),則執行新生代和老年代的混合收集(MixedGC),比如我們之前說的堆默認有2048個region,如果有接近1000個region都是老年代的region,則可能就要觸發MixedGC了

    -XX:G1MixedGCLiveThresholdPercent(默認85%)  region中的存活物件低于這個值時才會回收該region,如果超過這個值,存活物件過多,回收的的意義不大,

    -XX:G1MixedGCCountTarget:在一次回收程序中指定做幾次篩選回收(默認8),在最后一個篩選回收階段可以回收一會,然后暫停回收,恢復系統運行,一會再開始回收,這樣可以讓系統不至于單次停頓時間過長,

    -XX:G1HeapWastePercent(默認5%): gc程序中空出來的region是否充足閾值,在混合回收的時候,對Region回收都是基于復制演算法進行的,都是把要回收的Region里的存活物件放入其他Region,然后這個Region中的垃圾物件全部清理掉,這樣的話在回收程序就會不斷空出來新的Region,一旦空閑出來的Region數量達到了堆記憶體的5%,此時就會立即停止混合回收,意味著本次混合回收就結束了,

13.2.10、G1垃圾收集器優化建議( -XX:MaxGCPauseMills=50ms)

? 假設引數 -XX:MaxGCPauseMills 設定的值很大,導致系統運行很久,年輕代可能都占用了堆記憶體的60%了,此時才觸發年輕代gc,
? 那么存活下來的物件可能就會很多,此時就會導致Survivor區域放不下那么多的物件,就會進入老年代中,

? 或者是你年輕代gc過后,存活下來的物件過多,導致進入Survivor區域后觸發了動態年齡判定規則,達到了Survivor區域的50%,也會快速導致一些物件進入老年代中,
? 所以這里核心還是在于調節 -XX:MaxGCPauseMills 這個引數的值,在保證他的年輕代gc別太頻繁的同時,還得考慮每次gc過后的存活物件有多少,避免存活物件太多快速進入老年代,頻繁觸發mixed gc.

13.2.11、什么場景適合使用G1

  1. 50%以上的堆被存活物件占用
  2. 物件分配和晉升的速度變化非常大
  3. 垃圾回收時間特別長,超過1秒
  4. 8GB以上的堆記憶體(建議值)
  5. 停頓時間是500ms以內

13.3、ZGC

定義:具有實驗性質的低延遲垃圾收集器

13.3.1、主要目標

  • 支持TB量級的堆,我們生產環境的硬碟還沒有上TB呢,這應該可以滿足未來十年內,所有JAVA應用的需求了吧,
  • 最大GC停頓時間不超10ms,目前一般線上環境運行良好的JAVA應用Minor GC停頓時間在10ms左右,Major GC一般都需要100ms以上(G1可以調節停頓時間,但是如果調的過低的話,反而會適得其反),之所以能做到這一點是因為它的停頓時間主要跟Root掃描有關,而Root數量和堆大小是沒有任何關系的,
  • 奠定未來GC特性的基礎,
  • 最糟糕的情況下吞吐量會降低15%,這都不是事,停頓時間足夠優秀,至于吞吐量,通過擴容分分鐘解決,另外,Oracle官方提到了它最大的優點是:它的停頓時間不會隨著堆的增大而增長!也就是說,幾十G堆的停頓時間是10ms以下,幾百G甚至上T堆的停頓時間也是10ms以下,

13.3.2、color poin(顏色指標)

Colored Pointers,即顏色指標,如下圖所示,ZGC的核心設計之一,以前的垃圾回收器的GC資訊都保存在物件頭中,而ZGC的GC資訊保存在指標中,

在這里插入圖片描述每個物件有一個64位指標,這64位被分為:

  • 18位:預留給以后使用;
  • 1位:Finalizable標識,此位與并發參考處理有關,它表示這個物件只能通過finalizer才能訪問;
  • 1位:Remapped標識,設定此位的值后,物件未指向relocation set中(relocation set表示需要GC的
    Region集合);
  • 1位:Marked1標識;
  • 1位:Marked0標識,和上面的Marked1都是標記物件用于輔助GC;
  • 42位:物件的地址(所以它可以支持2^42=4T記憶體):

為什么有2個mark標記?

每一個GC周期開始時,會交換使用的標記位,使上次GC周期中修正的已標記狀態失效,所有參考都變成未標記,
GC周期1:使用mark0, 則周期結束所有參考mark標記都會成為01,
GC周期2:使用mark1, 則期待的mark標記10,所有參考都能被重新標記,
通過對配置ZGC后物件指標分析我們可知,物件指標必須是64位,那么ZGC就無法支持32位作業系統,同樣的也就無法支持壓縮指標了(CompressedOops,壓縮指標也是32位),

顏色指標的三大優勢:

  1. 一旦某個Region的存活物件被移走之后,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指向該Region的參考都被修正后才能清理,這使得理論上只要還有一個空閑Region,ZGC就能完成收集,
  2. 顏色指標可以大幅減少在垃圾收集程序中記憶體屏障的使用數量,ZGC只使用了讀屏障,
  3. 顏色指標具備強大的擴展性,它可以作為一種可擴展的存盤結構用來記錄更多與物件標記、重定位程序相關的資料,以便日后進一步提高性能,

讀屏障
之前的GC都是采用Write Barrier,這次ZGC采用了完全不同的方案讀屏障,這個是ZGC一個非常重要的特性,
在標記和移動物件的階段,每次「從堆里物件的參考型別中讀取一個指標」的時候,都需要加上一個Load Barriers,那么我們該如何理解它呢?看下面的代碼,第一行代碼我們嘗試讀取堆中的一個物件參考obj.fieldA并賦給參考o(fieldA也是一個物件時才會加上讀屏障),如果這時候物件在GC時被移動了,接下來JVM就會加上一個讀屏障,這個屏障會把讀出的指標更新到物件的新地址上,并且把堆里的這個指標“修正”到原本的欄位里,這樣就算GC把物件移動了,讀屏障也會發現并修正指標,于是應用代碼就永遠都會持有更新后的有效指標,而且不需要STW,
那么,JVM是如何判斷物件被移動過呢?就是利用上面提到的顏色指標,如果指標是Bad Color,那么程式還不能往下執行,需要「slow path」,修正指標;如果指標是Good Color,那么正常往下執行即可:

在這里插入圖片描述

? 這個動作是不是非常像JDK并發中用到的CAS自旋?讀取的值發現已經失效了,需要重新讀取,而ZGC這里是之前持有的指標由于GC后失效了,需要通過讀屏障修正指標,?
后面3行代碼都不需要加讀屏障:Object p = o這行代碼并沒有從堆中讀取資料:o.doSomething()也沒有從堆中讀取資料;obj.fieldB不是物件參考,而是原子型別,
正是因為Load Barriers的存在,所以會導致配置ZGC的應用的吞吐量會變低,官方的測驗資料是需要多出額外4%的開銷:

在這里插入圖片描述

那么,判斷物件是Bad Color還是Good Color的依據是什么呢?就是根據上一段提到的Colored Pointers的4個顏色位,

當加上讀屏障時,根據物件指標中這4位的資訊,就能知道當前物件是Bad/Good Color了,

PS:既然低42位指標可以支持4T記憶體,那么能否通過預約更多位給物件地址來達到支持更大記憶體的目的呢?答案肯定是不可以,因為目前主板地址總線最寬只有48bit,4位是顏色位,就只剩44位了,所以受限于目前的硬體,ZGC最大只能支持16T的記憶體,JDK13就把最大支持堆記憶體從4T擴大到了16T,

13.3.3、運作程序

在這里插入圖片描述

  1. 并發標記(Concurrent Mark):與G1一樣,并發標記是遍歷物件圖做可達性分析的階段,它的初始標記(Mark Start)和最終標記(Mark End)也會出現短暫的停頓,與G1不同的是, ZGC的標記是在指標上而不是在物件上進行的, 標記階段會更新染色指標中的Marked 0、 Marked 1標志位,
  2. 并發預備重分配(Concurrent Prepare for Relocate):這個階段需要根據特定的查詢條件統計得出本次收集程序要清理哪些Region,將這些Region組成重分配集(Relocation Set),ZGC每次回收都會掃描所有的Region,用范圍更大的掃描成本換取省去G1中記憶集的維護成本,
  3. 并發重分配(Concurrent Relocate):重分配是ZGC執行程序中的核心階段,這個程序要把重分配集中的存活物件復制到新的Region上,并為重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊物件到新物件的轉向關系,ZGC收集器能僅從參考上就明確得知一個物件是否處于重分配集之中,如果用戶執行緒此時并發訪問了位于重分配集中的物件,這次訪問將會被預置的記憶體屏障(讀屏障)所截獲,然后立即根據Region上的轉發表記錄將訪問轉發到新復制的物件上,并同時修正更新該參考的值,使其直接指向新物件,ZGC將這種行為稱為指標的“自愈”(Self-Healing)能力,
    1 ZGC的顏色指標因為“自愈”(Self‐Healing)能力,所以只有第一次訪問舊物件會變慢, 一旦重分配集中某個Region的存活物件都復制完畢后,
    2 這個Region就可以立即釋放用于新物件的分配,但是轉發表還得留著不能釋放掉, 因為可能還有訪問在使用這個轉發表,
  4. 并發重映射(Concurrent Remap):重映射所做的就是修正整個堆中指向重分配集中舊物件的所有參考,但是ZGC中物件參考存在“自愈”功能,所以這個重映射操作并不是很迫切,ZGC很巧妙地把并發重映射階段要做的作業,合并到了下一次垃圾收集回圈中的并發標記階段里去完成,反正它們都是要遍歷所有物件的,這樣合并就節省了一次遍歷物件圖的開銷,一旦所有指標都被修正之后, 原來記錄新舊物件關系的轉發表就可以釋放掉了,

13.3.4、存在的問題,怎么解決

ZGC最大的問題是浮動垃圾,ZGC的停頓時間是在10ms以下,但是ZGC的執行時間還是遠遠大于這個時間的,假如ZGC全程序需要執行10分鐘,在這個期間由于物件分配速率很高,將創建大量的新物件,這些物件很難進入當次GC,所以只能在下次GC的時候進行回收,這些只能等到下次GC才能回收的物件就是浮動垃圾,
ZGC沒有分代概念,每次都需要進行全堆掃描,導致一些“朝生夕死”的物件沒能及時的被回收,

解決方案
目前唯一的辦法是增大堆的容量,使得程式得到更多的喘息時間,但是這個也是一個治標不治本的方案,如果需要從根本上解決這個問題,還是需要引入分代收集,讓新生物件都在一個專門的區域中創建,然后專門針對這個區域進行更頻繁、更快的收集,

13.3.5、安全點與安全區域

安全點就是指代碼中一些特定的位置,當執行緒運行到這些位置時它的狀態是確定的,這樣JVM就可以安全的進行一些操作,比如GC等,所以GC不是想什么時候做就立即觸發的,是需要等待所有執行緒運行到安全點后才能觸發,
這些特定的安全點位置主要有以下幾種:

  1. 方法回傳之前
  2. 呼叫某個方法之后
  3. 拋出例外的位置
  4. 回圈的末尾

大體實作思想是當垃圾收集需要中斷執行緒的時候, 不直接對執行緒操作, 僅僅簡單地設定一個標志位, 各個執行緒執行程序時會不停地主動去輪詢這個標志, 一旦發現中斷標志為真時就自己在最近的安全點上主動中斷掛起, 輪詢標志的地方和安全點是重合的,

安全區域又是什么?

Safe Point 是對正在執行的執行緒設定的,
如果一個執行緒處于 Sleep 或中斷狀態,它就不能回應 JVM 的中斷請求,再運行到 Safe Point 上,因此 JVM 引入了 Safe Region,
Safe Region 是指在一段代碼片段中,參考關系不會發生變化,在這個區域內的任意地方開始 GC 都是安全的,

13.3.6、ZGC引數

在這里插入圖片描述

13.3.7、ZGC觸發時機

ZGC目前有4中機制觸發GC:

1、定時觸發,默認為不使用,可通過ZCollectionInterval引數配置,
2、預熱觸發,最多三次,在堆記憶體達到10%、20%、30%時觸發,主要時統計GC時間,為其他GC機制使用,
3、分配速率,基于正態分布統計,計算記憶體99.9%可能的最大分配速率,以及此速率下記憶體將要耗盡的時間點,在耗盡之前觸發GC(耗盡時間 - 一次GC最大持續時間 - 一次GC檢測周期時間),
4、主動觸發,(默認開啟,可通過ZProactive引數配置) 距上次GC堆記憶體增長10%,或超過5分鐘時,對比距上次GC的間隔時間跟(49 * 一次GC的最大持續時間),超過則觸發,

14、如何選擇垃圾收集器

  1. 優先調整堆的大小讓服務器自己來選擇

  2. 如果記憶體小于100M,使用串行收集器

  3. 如果是單核,并且沒有停頓時間的要求,串行或JVM自己選擇

  4. 如果允許停頓時間超過1秒,選擇并行或者JVM自己選

  5. 如果回應時間最重要,并且不能超過1秒,使用并發收集器

  6. 4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,幾百G以上用ZGC

在這里插入圖片描述

JDK 1.8默認使用 Parallel(年輕代和老年代都是)
JDK 1.9默認使用 G1

15、各種命令(例如100%cpu的排查、死鎖的檢查)

15.1、100%CPU的排查

1 、 使用top命令查看cpu占用資源較高的PID

2、通過jps 找到當前用戶下的java程式PID(jps -l 能夠列印出所有的應用的PID)

3、使用 pidstat -p

4、找到cpu占用較高的執行緒TID

5、將TID轉換為十六進制的表示方式

6、通過jstack -l(使用jstack 輸出當前PID的執行緒dunp資訊)

7、 查找 TID對應的執行緒(輸出的執行緒id為十六進制),找到對應的代碼

15.2、死鎖的檢查

方法一、使用 jps + jstack

? 在windons命令視窗,使用 jps -l (找到運行的程式的PID)

? 使用jstack -l PID(上面的)

方法二:使用jconsole

方法三:使用Java Visual VM

16、JIT(即時編譯器)

JIT是一種提高程式運行效率的方法,通常,程式有兩種運行方式:靜態編譯與動態解釋,靜態編譯的程式在執行前全部被翻譯為機器碼,而動態解釋執行的則是一句一句邊運行邊翻譯,

17、逃逸分析

逃逸分析是指在某個方法之內創建的物件,除了在方法體之內被參考之外,還在方法體之外被其它變數參考到;這樣帶來的后果是在該方法執行完畢之后,該方法中創建的物件將無法被GC回收,由于其被其它變數參考,正常的方法呼叫中,方法體中創建的物件將在執行完畢之后,將回收其中創建的物件;故由于無法回收,即成為逃逸,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/252014.html

標籤:java

上一篇:《C++ Primer》筆記 第8章 IO庫

下一篇:AWS 宣布創建 Elasticsearch 和 Kibana 分支

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more