assets/上内存与垃圾回收/image-20240113110314765.jpeg

Java 原始檔 (. java):包含高階的 Java 程式語言程式碼,由程式設計師撰寫。這是人類可讀的原始碼

Java -- javac --> class 執行 --> JIT 同時編譯 -->機器碼 --> 硬體執行

assets/上内存与垃圾回收/image-20231220171955940.png

位元組碼檔案 (.class):由 Java 編譯器將 Java 原始檔編譯產生的二進位檔案,其中包含了 Java 虛擬機器可以執行的位元組碼。位元組碼是一種由高階語言(例如 Java)原始碼編譯產生的、介於原始碼和機器碼之間的中間碼形式。

JVM 的整體結構

assets/上内存与垃圾回收/image-20231207121351317.png

Java 程式碼執行流程

assets/上内存与垃圾回收/image-20231207123700583.png

JVM 架構模型

java 編輯器輸入的指令流基於兩種架構 堆疊/暫存器的指令集架構

Java 編譯器輸入的指令流是指在編譯 Java 原始碼時,Java 編譯器產生的位元組碼 指令序列,用於描述原始碼的 執行邏輯

java 編譯器輸入的 指令流 基於 堆疊結構 轉化為位元組碼檔案

assets/上内存与垃圾回收/image-20231207123942154.png

為何不更換暫存器架構

assets/上内存与垃圾回收/image-20231207124830585.png

JVM 的生命週期

  • 虛擬機器的啟動 assets/上内存与垃圾回收/image-20231209203259299.png
  • 虛擬機器的執行 assets/上内存与垃圾回收/image-20231209203445250.png
  • 虛擬機器的結束 assets/上内存与垃圾回收/image-20231209203609741.png

JVM 發展歷程 (了解)

JVM 發展歷程 (了解)
  • Sun Classic VM assets/上内存与垃圾回收/image-20231209204100433.png 只用解譯器,會一行一行執行,即使 for 迴圈也一樣,而 JIT 編譯器可以尋找熱點程式碼,也就是執行頻率高的程式碼,會將其編成本地機器指令,然後快取起來,下次反覆執行就不會先由解譯器逐行再去翻譯,但所有程式碼都會被編譯為機器碼需要時間長 assets/上内存与垃圾回收/image-20231209204807824.png

  • Exact VM assets/上内存与垃圾回收/image-20231209204844327.png

  • HotSpot 目前稱霸 assets/上内存与垃圾回收/image-20231209205205237.png

  • JRockit assets/上内存与垃圾回收/image-20231209205528413.png

  • IBM的J9

assets/上内存与垃圾回收/image-20231209205634222.png

  • KVM和CDC/CLDC Hotspot assets/上内存与垃圾回收/image-20231209210210861.png

  • Azul VM assets/上内存与垃圾回收/image-20231209210247970.png

  • Liquid VM assets/上内存与垃圾回收/image-20231209210306815.png

  • Apache Harmony assets/上内存与垃圾回收/image-20231209210328950.png

  • Microsoft JVM assets/上内存与垃圾回收/image-20231209210335768.png

  • TaobaoJVM assets/上内存与垃圾回收/image-20231209210421922.png

assets/上内存与垃圾回收/image-20231209210604383.png

assets/上内存与垃圾回收/image-20231209210627354.png

類別載入子系統

javap -v xxx.class

解析:反組譯(將位元組碼檔案內容轉換為人類可讀的形式) idea中叫反編譯,class檔案和java檔案看起來一樣

類別載入器與類別的載入過程

ClassLoader 對 class 檔案進行載入 (不管能否執行, 看Execution Engine),需要載入類別的資訊存放於方法區

補充載入. class 檔案的方式

assets/上内存与垃圾回收/image-20231209214324593.png

類別載入器與類別的載入過程

assets/上内存与垃圾回收/image-20231209211021721.png 特定檔案標識 : 二進位開頭 ca fe ba be , 所有能被 Java 虛擬機器識別的都有這個標識 assets/上内存与垃圾回收/image-20231209220841318.png

assets/上内存与垃圾回收/image-20231209213206653.png 實體磁碟上 car.class 檔案以二進位流的方式載入到記憶體中 實例化對應: 記憶體中呼叫 car 的建構子或建構方法 assets/上内存与垃圾回收/image-20231209213218500.png

載入 Loading

透過類別的 全限定名 取得該類別的 binary byte stream (class loading is lazy: loaded on demand [active use]), which converts what the byte stream represents as static storage structure into runtime data of the method area, and then generates in memory the class object

static method belongs to the class itself, not to instances of the class. They can be used once the class has finished loading. JVM memory and garbage collection > ^ne4cv6

loading Loading

assets/上内存与垃圾回收/image-20231209213652892.png

linking linking (verification -->preparation -->resolution)

assets/上内存与垃圾回收/image-20231209220957068.png

verification such as ca fe ba be

the virtual method table is created at this stage

initialization Initialization

assets/上内存与垃圾回收/image-20231211181431914.png

  1. object creation
  2. executes the class constructor method(constructor) and assignment of static variables

is responsible for completing the initialization of the object, including allocating space for instance variables and performing default assignment. It can also explicitly assign values to instance variables.

<clinit>method

<clinit>method

assets/上内存与垃圾回收/image-20231211175531034.png

the method content is as follows

assets/上内存与垃圾回收/image-20231211180054749.png

if there is no merging of class variable (static variable) assignment actions and static code block statements, there will be no<clinit>method, such as

assets/上内存与垃圾回收/image-20231211181122305.png assets/上内存与垃圾回收/image-20231211181143322.png in other words, only with static variablesassignmentwill there be<clinit>method, and if there is no assignment there is also none

the virtual machine guarantees that a class<clinit>method is synchronized and locked during multithreading

assets/上内存与垃圾回收/image-20231211200701028.png assets/上内存与垃圾回收/image-20231211200711061.png only one thread executes, and the virtual machine guarantees that the class is executed only once when it is loaded<clinit>method, the methods in static are<clinit>a manifestation of the method, and static executes only once, which shows that <clinit>the method executes only once

class loader classification

bootstrap class loader: this class loader is implemented in C and C++ language and is nested inside the JVM extension class extloader: responsible for loading Java extension libraries, which are usually optional functional extensions that are not part of the Java core but are commonly used. system class appclassLoader loader, the default loader for programs: generally speaking, the classes of a Java application are loaded by it

ext and app do not have an inheritance relationship, but ext is app the upper layer of, only having a reference

ext and app both and classLoader(abstract class) have an inheritance relationship

class loader classification

assets/上内存与垃圾回收/image-20231211201345113.png

  • bootstrap class loader: Java core class libraries are all loaded using the bootstrap class loader assets/上内存与垃圾回收/image-20231212143616097.png assets/上内存与垃圾回收/image-20240115151530254.png

  • extension class loader: ExtClassLoade assets/上内存与垃圾回收/image-20231212143944009.png

  • system class loader: AppClassLoade (application class loader)(user default loader) assets/上内存与垃圾回收/image-20231212144056161.png

關係

assets/上内存与垃圾回收/image-20231211201328890.png assets/上内存与垃圾回收/image-20231211202423609.png assets/上内存与垃圾回收/image-20231211202431711.png

載入器

    public static void main(String[] args) {  
  
        //取得系統類別載入器  
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();  
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2  
  
        //取得其上層:擴充類別載入器  
        ClassLoader extClassLoader = systemClassLoader.getParent();  
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d  
  
        //取得其上層:取得不到啟動類別載入器  
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();  
        System.out.println(bootstrapClassLoader);//null  
  
        //對於使用者自訂類別來說:預設使用系統類別載入器進行載入  
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();  
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2  
		System.out.println(classLoader.getParent());//sun.misc.Launcher$ExtClassLoader@1b6d3586
		
        //String類別使用啟動類別載入器進行載入。---> Java的核心類別庫都是使用啟動類別載入器進行載入的
        ClassLoader classLoader1 = String.class.getClassLoader();  
        System.out.println(classLoader1);//null  
    }  
驗證對應路徑的類別是對應載入器載入的
public static void main(String[] args) {  
    System.out.println("**********启动类加载器**************");  
    //取得BootstrapClassLoader能夠載入的api的路徑  
    URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();  
    for (URL element : urLs) {  
        System.out.println(element.toExternalForm());  
    }  
    //從上面的路徑中隨意選擇一個類別,來看看它的類別載入器是什麼:啟動類別載入器  
    ClassLoader classLoader = Provider.class.getClassLoader();  
    System.out.println(classLoader);//null  
  
    System.out.println("***********扩展类加载器*************");  
    //System.getProperty():取得指定的Java系統屬性  
    //"java.ext.dirs":這個屬性鍵表示Java擴充目錄屬性  
    String extDirs = System.getProperty("java.ext.dirs");  
    for (String path : extDirs.split(";")) {  
        System.out.println(path);  
    }  
    //從上面的路徑中隨意選擇一個類別,來看看它的類別載入器是什麼:擴充類別載入器  
    ClassLoader classLoader1 = SunEC.class.getClassLoader();  
    System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1b6d3586  
}
使用者自訂類別載入器

new UserClassLoader(載入路徑)

assets/上内存与垃圾回收/image-20231212180828250.png assets/上内存与垃圾回收/image-20231212181342511.png assets/上内存与垃圾回收/image-20240115151939320.png

取得 ClassLoader 抽象類別

ClassLoader 抽象類別

除了啟動類別載入器,所有類別載入器都繼承於 ClassLoader java 中可以分為兩類載入器,一類啟動類別載入器,一類繼承 ClassLoader 類別的

assets/上内存与垃圾回收/image-20231212181956092.png

  • 取得 classLoader 途徑 assets/上内存与垃圾回收/image-20231212182102218.png
//1.  
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();  
System.out.println(classLoader); 
//2.  
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();  
System.out.println(classLoader1); //sun.misc.Launcher$AppClassLoader@18b4aac2
  
//3.  
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader();//sun.misc.Launcher$AppClassLoader@18b4aac2  
ClassLoader classLoader3 = ClassLoader.getSystemClassLoader().getParent();  
System.out.println(classLoader2);  
System.out.println(classLoader3);

雙親委派機制

類別是按需載入的,同時是雙親委派機制,類別會委託給父類別載入器,不斷向上委託,頂層父類別判斷自己能否載入,不能則委託給子類別

簡介

assets/上内存与垃圾回收/image-20231212183828055.png assets/上内存与垃圾回收/image-20231212184757649.png

證明

assets/上内存与垃圾回收/image-20231212184818764.png assets/上内存与垃圾回收/image-20231212184826153.png

結果自己建立的 String 沒有自身系統類別載入器使用,而是委託最上層父類別載入器去載入,最上層載入器會看所屬套件,如果不屬於自己的管理範疇,則會繼續向下委託給擴充類別載入器,擴充類別載入器看如果也不屬於自己的管理範疇,則也不管,才委託給系統類別載入器 assets/上内存与垃圾回收/image-20231212185438196.png 委託給啟動類別載入器後,發現 java 中真正的 String 中根本沒有 main 方法,就直接忽略掉了自己寫的 string,如果官方版本存在,就會忽略自訂版本

assets/上内存与垃圾回收/image-20231212185700345.png

反向委派

介面是核心 API,使用啟動類別載入器載入,但具體實作類別是第三方 Jar 套件,就會透過 執行緒情境載入器 ,反向委派 到 系統類別載入器

assets/上内存与垃圾回收/image-20231212185854159.png

優勢

  1. 避免了類別的重複載入,每個載入器都有自己的載入範疇
  2. 保護程式安全,防止核心 API 被篡改,假如傳過來的自訂 String 不會被載入,還是啟動類別載入器載入核心 API
沙箱安全機制

會沒有權限,直接報錯,禁止了自訂類別使用核心 API 的套件名稱

assets/上内存与垃圾回收/image-20231212190436929.png

assets/上内存与垃圾回收/image-20231212190903229.png

其他

一個類別物件必須來源於同一個 class 檔案和同一個 classLoader 才相等

使用者類別載入器載入的類別會將這個類別載入器的一個參考作為型別資訊的一部分保存在方法區

Java 程式對類的主動使用會導致初始化,被動使用不會導致類的初始化 ^ne4cv6

其他

assets/上内存与垃圾回收/image-20231212191522995.png assets/上内存与垃圾回收/image-20231212204252614.png assets/上内存与垃圾回收/image-20231212201454523.png

執行時資料區

範例圖

assets/上内存与垃圾回收/image-20240113110338724.jpeg

紅色表示一個行程一份 灰色表示一個執行緒一份

assets/上内存与垃圾回收/image-20240113110435100.jpeg

assets/上内存与垃圾回收/image-20231212211357802.png ^dugjvf

assets/上内存与垃圾回收/image-20231212212042610.png

一個 JVM 只有一個 Runtime 實例,也就是執行時環境

執行緒

在 Hotspot JVM 裡,每個執行緒都與作業系統的原生執行緒直接對應。當一個 Java 執行緒準備好執行以後,此時一個作業系統的原生執行緒也同時建立。Java 執行緒執行終止後,原生執行緒也會回收。

執行緒

在Hotspot JVM裡,每個執行緒都與作業系統的原生執行緒直接對應。 當一個Java執行緒準備好執行以後,此時一個作業系統的原生執行緒也同時建立。Java執行緒執行終止後,原生執行緒也會回收。 作業系統負責所有執行緒的安排調度到任何一個可用的CPU上。一旦原生執行緒初始化成功,它就會呼叫Java執行緒中的run ()方法

assets/上内存与垃圾回收/image-20231213085846708.png

程式計數器 (PC 暫存器)

為了能夠準確地記錄各個執行緒正在執行的當前位元組碼指令位址,

每一個執行緒都分配一個 PC 暫存器

儲存執行指向執行下一條指令的位址,位址由執行引擎讀取,可以理解成游標或者集合的迭代器,是程式控制流的指示器(分支,跳轉,迴圈,異常)

不存在 error,速度最快 既沒有 GC(不需要垃圾回收,不斷被替換為下一個位址) 唯一沒有 OOM(沒有溢出)

程式計數器 (PC 暫存器)

assets/上内存与垃圾回收/image-20231213091027135.png assets/上内存与垃圾回收/image-20231213091137072.png assets/上内存与垃圾回收/image-20231213131815549.png

簡介

assets/上内存与垃圾回收/image-20231213132849928.png assets/上内存与垃圾回收/image-20231213132857146.png

指定位址(偏移位址)PC暫存器儲存的資料

assets/上内存与垃圾回收/image-20231213135847284.png

PC暫存器儲存位元組碼指令位址的作用

assets/上内存与垃圾回收/image-20231213140130780.png

為了準確記錄各個執行緒當前執行的位元組碼指令位址,各個執行緒這樣不會互相干擾

PC暫存器為什麼被設定為執行緒私有

assets/上内存与垃圾回收/image-20231213140324947.png 看著像是並行,實際是一個一個執行

虛擬機堆疊

assets/上内存与垃圾回收/image-20231217112808882.png

一個執行緒對應一個 Java 虛擬機堆疊,有 Error,不存在 GC,存在 OOM,速度僅次於程式計數器

堆疊是執行時單位,堆積是儲存的單位

assets/上内存与垃圾回收/image-20240819122125536.png

簡介

assets/上内存与垃圾回收/image-20231213141817641.png assets/上内存与垃圾回收/image-20231213141826528.png

assets/上内存与垃圾回收/image-20240109203749992.png 食材明細 --> 變數 步驟 -->位元組碼指令 食材 -- > 堆積

assets/上内存与垃圾回收/image-20231213162158171.png

  • 優點 assets/上内存与垃圾回收/image-20240109204039263.png
  • 堆疊中可能出現的異常 堆疊的大小可設定動態或固定 固定時,超過 Java 虛擬機堆疊允許的最大容量會拋出 StackOverflowError 若動態拓展申請不到足夠記憶體會拋出 OutOfMemoryError

java (oracle.com)

-Xss 堆疊的記憶體大小設定

assets/上内存与垃圾回收/image-20231213163405091.png assets/上内存与垃圾回收/image-20231213163706783.png assets/上内存与垃圾回收/image-20231213163834164.png assets/上内存与垃圾回收/image-20231214095305598.png assets/上内存与垃圾回收/image-20231214101440893.png 只呼叫了1000多次

堆疊的記憶體決定堆疊框架總大小,單個堆疊框架大小決定堆疊框架數量

堆疊的儲存單位: 堆疊框架

每個執行緒都有自己的堆疊,堆疊中的資料都是以 堆疊框架(Stack Frame) 的格式存在,執行緒上的每個方法都各自對應一個 堆疊框架(Stack Frame) ,堆疊框架是一個記憶體區域,是一個資料集,維護方法執行過程中的各種資料資訊

assets/上内存与垃圾回收/image-20231213164508864.png

暫存器存的也是當前堆疊框架 assets/上内存与垃圾回收/image-20240113110447510.jpeg assets/上内存与垃圾回收/image-20240113110501636.jpeg

以此類推

assets/上内存与垃圾回收/image-20231213165514623.png 方法如果沒處理異常會拋給 main, 沒處理都會拋出異常的方式結束

  • 棧幀內部結構

assets/上内存与垃圾回收/image-20231213170319906.png

棧幀內部結構: 局部變數表

棧幀為一個 數字陣列(大小編譯期確定),也稱局部變數陣列或本地變數表,主要用於儲存 呼叫方法的參數 (方法執行時,虛擬機使用局部變數表完成方法的傳遞) 和定義在方法體內的 局部變數和值, 只在當前棧幀中有效

棧幀中與效能調校關係最密切的就是局部變數表

棧越大,方法巢狀呼叫次數越多

局部變數表中的變數是垃圾回收的根節點 (只要被局部變數表中直接或間接引用的物件都不會被回收)

局部變數表

assets/上内存与垃圾回收/image-20231213224544105.png assets/上内存与垃圾回收/image-20231214095336922.png

解析

assets/上内存与垃圾回收/image-20231214095407597.png 局部變數表長度 locals=3 assets/上内存与垃圾回收/image-20231214094124542.png assets/上内存与垃圾回收/image-20231214094148509.png L代表參照型別 局部變數表的容量大小在編譯器就確認下來了 assets/上内存与垃圾回收/image-20231214094904892.png assets/上内存与垃圾回收/image-20231214094908054.png 最後還有一個收尾的大括號代表一個

簡介

assets/上内存与垃圾回收/image-20240110171346908.png assets/上内存与垃圾回收/image-20240110171352914.png

細節理解

變數個數 assets/上内存与垃圾回收/image-20231214104200960.png 該java程式碼所對應的位元組碼長度 assets/上内存与垃圾回收/image-20231214104400913.png start (位元組碼指令行號) 和 line Number (程式碼行號)的對應 assets/上内存与垃圾回收/image-20231214102819119.png

info

位元組碼指令對應的就是java指令內容

LineNumberTable : 位元組碼行和程式碼行對應表 assets/上内存与垃圾回收/image-20240109205510039.png 局部變數的描述 assets/上内存与垃圾回收/image-20231214102357366.png

根據索引使用相應變數

start pc是位元組碼指令行數 length描述當前變數作用域的範圍(即pc開始往後的位元組碼範圍) 如pc 8, length8 ,即位元組碼行號為8-8+8的作用範圍 start pc其實就是起始的範圍 index 是索引

如num --> pc11 --> line 16:即作用域的開始是聲明的下一行

assets/上内存与垃圾回收/image-20231214102819119.png assets/上内存与垃圾回收/image-20231214102934323.png

start pc + length = code length

assets/上内存与垃圾回收/image-20231214103232391.png assets/上内存与垃圾回收/image-20231214103221484.png assets/上内存与垃圾回收/image-20231214103216261.png

局部變數表單位 slot

局部變數表

int i=1

杜宇基本資料型別儲存 i 和對應的值 1 對於參照型別局部變數表儲存位址值

局部變數表的最基本的單位是 Slot

byte 1,short 2,int 4,long 8,float 4 double 8,char 2 位元組,大小對應slot

參照型別32位 佔用一個 slot

關於 slot 的理解

assets/上内存与垃圾回收/image-20231214111054876.png 存取佔用 64 位的局部變數值,只需要使用前一個索引即可 assets/上内存与垃圾回收/image-20231214111715181.png assets/上内存与垃圾回收/image-20231214111820633.png assets/上内存与垃圾回收/image-20231214111813048.png 如果當前幀是由建構方法或者實例方法建立的,那麼該物件參照 this 將會存放在 index 為 0 的 slot 處,其餘的參數按照參數表順序繼續排列

對於 static 方法 : this 變數則不存在於當前方法的局部變數表中,由於 static 方法是 屬於類 而不是實例的,它不需要通過 this 關鍵字引用當前物件。在 static 方法中,不存在當前物件的引用,因此局部變數表中不會分配 this 變數

slot演示

assets/上内存与垃圾回收/image-20231214112426926.png assets/上内存与垃圾回收/image-20231214112432995.png weight佔據兩個3,4

  • 建構子

assets/上内存与垃圾回收/image-20231214112532734.png assets/上内存与垃圾回收/image-20231214112537174.png

堆疊訊框中的局部變數表中的槽位是可以重用的,如果一個局部變數過了其作用域,那麼在其作用域之後宣告的新的局部變數就很有可能會複用過期局部變數的槽位,從而達到節省資源的目的 (因為開闢的陣列不能變, 所以需要重複利用)(槽中的資料不再使用且需要被其他物件使用時,需要被回收,如果只是不再使用沒有被回收)

槽位重用

b過了作用域還佔據著slot,目前還沒有被重複利用 assets/上内存与垃圾回收/image-20240109210531193.png

assets/上内存与垃圾回收/image-20240109210642240.png

c重複利用了b的index空間,

變數的分類:按照資料類型分:
① 基本資料類型  
② 引用資料類型  

    按照在類中宣告的位置分:
		① 成員變數:在使用前,都經歷過預設初始化賦值  
	        類變數: linking的prepare階段:給類變數預設賦值  ---> initial階段:給類變數顯式賦值即靜態程式碼區塊賦值  
	        實例變數:隨著物件的建立,會在堆空間中分配實例變數空間,並進行預設賦值
		② 局部變數:在使用前,必須要進行顯式賦值的!因為局部變數不會被預設賦值,編譯不通過

堆疊訊框內部結構: 運算元堆疊

每一個獨立的堆疊訊框包含一個後進先出 (last-In-First-Out) 的運算元堆疊,也可以稱之為表達式堆疊 (Expression Stack)

運算元堆疊 (Operand Stack) 的作用就是輔助完成局部變數的資料的各種,主要用於保存計算的臨時結果,作為變數臨時儲存空間,所需最大深度在編譯期間就確定好了

呼叫方法的返回值會被壓入運算元堆疊,並更新程式計數器下一條指令的位址 JVM/上記憶體與垃圾回收 > 方法返回位址

運算元堆疊 並非採用訪問索引 的方式來進行資料訪問的,而是只能通過入棧 (push) 和出棧 (pop) 操作來完成一次資料訪問

運算元堆疊

assets/上内存与垃圾回收/image-20231214114902101.png 既有棧的特點,也具有陣列或鏈結串列的特點 assets/上内存与垃圾回收/image-20231214120343996.png assets/上内存与垃圾回收/image-20231214120425280.png

程式碼追蹤

../../../Utility/Excalidraw/Drawing 2023-12-14 20.45.04.excalidraw

assets/上内存与垃圾回收/image-20231214204827968.png

簡介

0 bipush 15 //bipush(push byte)將一個位元組的常量值推送到運算元堆疊上(bipushbyte-->int ;sipush是short-->int) 2 istore_1 //Integer STORE_ X表示將運算元堆疊上資料儲存到局部變數表的索引X的位置 3 bipush 8 5 istore_2 6 iload_1 // iload_X將區域變數表的資料 X 放到運算元堆疊 7 iload_2 8 iadd // 將運算元堆疊的資料相加 9 istore_3 // 10 return

  • 區域變數表中,byte 會以 int 形式儲存
  • 運算元堆疊中,bipush 指令壓入的是 byte 類型

為什麼位元組碼指令之間的編號似乎不連續

assets/上内存与垃圾回收/image-20231214194250584.png

aload_0 的含義是將目前物件參考載入到運算元堆疊上

i++ 和 ++i 的位元組碼指令

對於左邊沒有賦值操作的 i++ 和 ++i 位元組碼指令一樣

對於左邊有賦值操作的 i++ 先load出來再incre by ++i 先incre by 再load出來

    public void add(){  
        //第1類問題:  
        int i1 = 10;  
        i1++;  
//        0 bipush 10  
//        2 istore_1  
//        3 iinc 1 by 1  
        int i2 = 10;  
        ++i2;  
//        6 bipush 10  
//        8 istore_2  
//        9 iinc 2 by 1  
  
        //第2類問題:  
        int i3 = 10;  
        int i4 = i3++;  
//        12 bipush 10  
//        14 istore_3  
//        15 iload_3  
//        16 iinc 3 by 1  
//        19 istore 4  
        int i5 = 10;  
        int i6 = ++i5;  
//        21 bipush 10  
//        23 istore 5  
//        25 iinc 5 by 1  
//        28 iload 5  
//        30 istore 6  
  
        //第3類問題:  
        int i7 = 10;  
        i7 = i7++;  
//        32 bipush 10  
//        34 istore 7  
//        36 iload 7  
//        38 iinc 7 by 1  
//        41 istore 7  
        int i8 = 10;  
        i8 = ++i8;  
//        43 bipush 10  
//        45 istore 8  
//        47 iinc 8 by 1  
//        50 iload 8  
//        52 istore 8  
        //第4類問題:  
        int i9 = 10;  
        int i10 = i9++ + ++i9;  
//        54 bipush 10  
//        56 istore 9  
//        58 iload 9  
//        60 iinc 9 by 1  
//        63 iinc 9 by 1  
//        66 iload 9  
//        68 iadd  
//        69 istore 10  
//        71 return  
    }

堆疊頂端快取技術 (Top-of-Stack Cashing)

將堆疊頂端元素全部快取在實體 CPU 暫存器 中,以降低對記憶體讀寫次數,提升執行引擎的執行效率

assets/上内存与垃圾回收/image-20231214204308886.png

動態連結 (或指向執行時常量池 的方法參考)

動態連結:將符號參考轉換為呼叫方法的直接參考

  • 符號參考: 用符號來描述被參考目標,不直接指向記憶體位址。
  • 直接參考: 直接指向記憶體位址或偏移量,可以被 JVM 直接使用。

每一個棧框內部都包含一個指向 執行時常量池中 該 棧框所屬方法的參考 包含這個參考的目的就是為了支援目前方法的程式碼能夠實現 動態連結 (Dynamic Linking), 動態連結的作用就是為了將這些符號參考轉換為呼叫方法的直接參考

例如 : invokedynamic 指令會攜帶一個符號參考, 到方法真正被呼叫時 JVM 會將這些符號參考轉換為呼叫方法的直接參考

也就是需要將符號參考轉換為呼叫方法的直接參考就是動態連結,就是 invokedynamic

有時動態連結和方法返回位址和一些附加資訊會被叫做幀資料區

方法參考:myClass.myMethod () 呼叫時,JVM 會使用執行時常量池中的方法符號參考來解析 myMethod 的實際記憶體位址 欄位參考:如果 MyClass 中有欄位存取,JVM 會使用執行時常量池中的欄位符號參考來解析欄位的實際記憶體位址

動態連結

assets/上内存与垃圾回收/image-20231225153931680.png assets/上内存与垃圾回收/image-20231214212141593.png

方法的呼叫

在 JVM 中,將符號參考轉換為呼叫方法的直接參考與方法的綁定機制有關

綁定

assets/上内存与垃圾回收/image-20231214231054189.png assets/上内存与垃圾回收/image-20231214231136724.png assets/上内存与垃圾回收/PixPin_2025-01-18_16-16-02.png

assets/上内存与垃圾回收/image-20231214231435931.png

invokestatic : 呼叫靜態 invokespecial : 呼叫 <init> 方法,私有,父類,唯一方法 invokevirtual 呼叫虛擬方法 invokeinterface 呼叫介面 invokedynamic 動態解析呼叫方法

"虛擬方法" (Virtual Method) 指的是可以被子類別覆寫 (Override) 的實例方法。也就是說,當呼叫一個物件的方法時,具體執行哪個方法版本 (父類別還是子類別) 要在執行時才能確定。這就是 "虛擬" 的含義,因為它不是在編譯時就確定下來的。

  • 特殊 呼叫自己類別獨有方法但為 public 的也是 invokevirtual

父類別中方法宣告為 final 的方法,不能被子类重写,可以被子类直接调用,直接调用会显示 invokevirtual,但此方法是非虚方法,也可以通过 super.,会显示为非虚方法

如果直接不通过 super. 调用父类的方法,编译期间不能确定子类是否重写父类方法,会显示为 invokevirtual

assets/上内存与垃圾回收/PixPin_2025-02-25_15-43-47.png

非虚方法

assets/上内存与垃圾回收/image-20231215094334850.png assets/上内存与垃圾回收/image-20231215095348147.png assets/上内存与垃圾回收/image-20231215102145996.png assets/上内存与垃圾回收/image-20231215104725519.png

Java 是静态, lamda 的引用使 Java 在一定程度上具有动态 js, python 都是动态

动态与静态类型语言

assets/上内存与垃圾回收/image-20231215104919603.png assets/上内存与垃圾回收/image-20231215105431030.png assets/上内存与垃圾回收/image-20231215105034817.png

调用一个对象的方法时,对象首先会压入操作数栈,然后根据字节码指令如 invokevirtual 找到這個物件的實際類型,然後找到常數池中和呼叫方法名稱一致的,進行存取權限校驗 (不通過 IllegalAccessError),通過後會按照繼承關係依次驗證,拋出 AbstactMethodError 說明找到最上層的介面發現是抽象方法,沒有被重寫

方法重寫的本質

assets/上内存与垃圾回收/image-20231215110205923.png

IllegalAccessError無權限存取

maven : jar 套件衝突可能出現 assets/上内存与垃圾回收/image-20231215110222513.png

  • 虛方法表

位元碼中包含了方法的符號引用,但並沒有包含方法的具體記憶體位址,設計出了虛方法表 虛方法表它包含了類別中所有的虛方法及其實際的 記憶體位址

虛方法表為了提高效能,避免程式碼中動態分配導致 JVM 執行時頻繁搜尋對應方法 ^j81ym3

記錄了對於某個類別中的方法是指向父類別還是自己類別的,不需要再進行尋找是屬於哪個類別的

如果沒有虛方法表,JVM 就必須在執行時進行複雜的判斷來確定具體呼叫哪個方法版本,這將大大降低程式的效能。

例如:

  • 每次呼叫方法時,都需要遍歷整個繼承鏈,找到最合適的方法,這個過程效率很低。
  • 當繼承層次很深時,效能損失會更嚴重。
虛方法表

assets/上内存与垃圾回收/image-20231215110501996.png assets/上内存与垃圾回收/image-20231215120154347.png assets/上内存与垃圾回收/image-20231215120613613.png assets/上内存与垃圾回收/image-20231215120627249.png assets/上内存与垃圾回收/image-20231215120739143.png

方法返回位址

A 方法中呼叫完 B 方法

normal exit : B no return value : execution engine accepts return address (that is A the address of), Execute the next stack frame based on this address B There is a return value : The execution engine receives the return value of method B , and this return value is passed to method A,method A performs subsequent processing based on this return value

Exit with exception: Since exiting with an exception does not produce a normal return value, method A does not receive a value from method B valid return value, only the exception table stores where it should go when an exception occurs, target where to go (exception table return address, the return address is determined through the exception table), to resolve it

method return address

assets/上内存与垃圾回收/image-20231215121318829.png assets/上内存与垃圾回收/image-20231215121510300.png

返回情況 assets/上内存与垃圾回收/image-20231215122138915.png assets/上内存与垃圾回收/image-20240109222054110.png 位元組碼行號

一些附加資訊

例如對程式除錯提供支援的資訊

本地方法堆疊

Java 虛擬機堆疊用於管理 Java 方法的呼叫 (執行緒私有) 本地方法堆疊用於管理本地方法的呼叫

簡介

assets/上内存与垃圾回收/image-20231215185921067.png 本地方法會壓入本地方法堆疊,由動態連結的方式呼叫 C 中的函式庫,由執行引擎執行 assets/上内存与垃圾回收/image-20231215190747359.png

堆積

一個行程由一個 JVM 實例只有一個堆積空間,在啟動時被建立,多個執行緒共用堆積空間 (共享意味著執行緒安全問題), 堆積也是 Java 記憶體管理的核心區域

JVM 啟動,空間大小已經確定 (實體記憶體空間雖然不連續,但邏輯視為連續空間) 所有的執行緒共享 Java 堆積,在這裡還可以劃分執行緒私有的緩衝區 Thread Local Allocation Buffer, TLAB

堆積簡介

assets/上内存与垃圾回收/image-20231217100953405.png assets/上内存与垃圾回收/image-20240113110624160.jpeg

Stack-Heap-Method area 關係

assets/上内存与垃圾回收/image-20231217113014404.png

方法結束後,指向堆積中的位址消失了, 堆積空間中實例繼續存在 (為了避免方法多 GC 頻率過高,所以堆積空間滿後再執行,避免影響使用者執行緒),當堆積空間滿後,需要 GC 判斷,發現沒有參考就回收

分代

assets/上内存与垃圾回收/image-20231217120246200.png assets/上内存与垃圾回收/image-20231217115148645.png

現在說的區沒有包括元空間或永久區

  • 新生代 assets/上内存与垃圾回收/image-20231217115825302.png Survivor 只會用一個

  • 養老代

  • assets/上内存与垃圾回收/image-20231217115843621.png

assets/上内存与垃圾回收/image-20231217120013954.png

設定堆積的大小與 OOM

  • 設定堆積空間大小的參數
    -Xms 用來設定堆積空間(年輕代加老年代)的initial記憶體大小
    -X isjvm的執行參數
    ms ismemory start

-Xmx 用來設定堆積空間(年輕代加老年代)的maximum記憶體大小

  • 預設堆積空間的大小
    初始記憶體大小: 物理电脑内存大小 /64 最大記憶體大小:物理电脑内存大小 /4

  • 手動設定:-Xms600m -Xmx600m 開發中 建议 將初始堆積記憶體和最大的堆積記憶體設定成相同的值 , 因為需要擴容和釋放,避免調整造成系統額外的壓力

  • 查看設定的參數 方式一:jps/jstat-gc 行程 id 方式二:-XX:+PrintGCDetails ^ikhhqe

assets/上内存与垃圾回收/image-20231217120036700.png
參數的設定只對 新生代, 養老代 生效

assets/上内存与垃圾回收/image-20231217133208373.png

查看堆積大小程式碼
public static void main(String[] args) {  
  
    //回傳Java虛擬機中的堆積記憶體總量  
    // 執行時資料區是單例  
    long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;  
    //回傳Java虛擬機試圖使用的最大堆積記憶體量  
    long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;  
  
    System.out.println("-Xms : " + initialMemory + "M");  
    System.out.println("-Xmx : " + maxMemory + "M");  
  
    System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G");  
    System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G");  
  
    try {  
        Thread.sleep(1000000);  
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    }  
}

年輕代與老年代

新生代 80%的物件都是朝生夕死

年輕代與老年代比例

assets/上内存与垃圾回收/image-20231217210854518.png 配置新生代老年代比例 assets/上内存与垃圾回收/image-20231217210942449.png assets/上内存与垃圾回收/image-20231217221604232.png

使用時發現不是 8 比 1, 需要手動指定


 -Xms600m -Xmx600m  
 -XX:NewRatio : 設定新生代與老年代的比例。預設值是  
 -XX:SurvivorRatio :設定新生代中Eden區與Survivor區的比例。預設值是8 ,實際需要手動指定8 
 -XX:-UseAdaptiveSizePolicy :關閉自適應的記憶體分配策略(暫時用不到)自適應兩個sur區可能不一樣大
 -Xmn:設定新生代的空間的大小(一般不設定)(優先高於比例)
 

圖解物件分配過程

由於記憶體分配演算法和垃圾回收密切相關

Minor GC,Major GC ,Full GC

GC

assets/上内存与垃圾回收/image-20231218134026840.png 伊甸園區滿後會觸發 GC 回收回收,順便回收 survival, 但 survival 滿時不會觸發 GC 回收 assets/上内存与垃圾回收/image-20231218134006437.png 年齡計數器: 數值 S0, S1 哪個空哪個是 to, 另一個 from, to 代表下一次放的地方 assets/上内存与垃圾回收/image-20231218140444665.png

assets/上内存与垃圾回收/image-20231218202133892.png

新物件放入流程

assets/上内存与垃圾回收/image-20231218140559308.png assets/上内存与垃圾回收/image-20231218144046816.png assets/上内存与垃圾回收/image-20231218145028478.png

  • 年輕代 GC(Minor GC) 觸發機制

年輕代空間不足就會觸發 minor GC

交替使用 Survivor 區可以減少記憶體碎片。每次 GC 後,存活的物件被緊湊地複製到另一個 Survivor 區,避免了記憶體碎片的產生。

年輕代 GC(Minor GC) 觸發機制

assets/上内存与垃圾回收/image-20231218202435028.png assets/上内存与垃圾回收/image-20231218203351299.png

  • 老年代 Major GC/Full GC 觸發機制

老年代空間不足時,觸發 Major GC (根據收集器不同, 應該至少觸發一次 Minor Gc)

Major GC 一般比 Minor Gc 要慢 10 倍以上 , Major GC 後,記憶體還不足會呼叫 FULL GC ,不足就爆 OOM

老年代 Major GC/Full GC 觸發機制

assets/上内存与垃圾回收/image-20231218203427145.png

  • Full GC 觸發機制

assets/上内存与垃圾回收/image-20231218203538319.png

OOM測試

老年代空間不足時會進行一次垃圾回收,回收後仍然不足才會爆OOM,爆OOM前必然會有Full GC -Xms9m -Xmx9m -XX:+PrintGCDetails


public static void main(String[] args) {  
    int i = 0;  
    try {  
        List<String> list = new ArrayList<>();  
        String a = "atguigu.com";  
        while (true) {  
            list.add(a);  
            a = a + a;  
            i++;  
        }  
  
    } catch (Throwable t) {  
        t.printStackTrace();  
        System.out.println("遍历次数为:" + i);  
    }  
}

[GC (Allocation Failure) [PSYoungGen: 2027K->504K(回收後新生代大小)(2560K)總空間大小] 2027K堆空間回收前(和前面一樣因為這時候堆空間老年代沒資料)->926K堆空間回收後,不一樣了,這時說明有資料放到老年代了(9728K)堆空間總大小, 0.0007621 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2210K->512K(2560K)] 2632K->1610K(9728K), 0.0006082 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2310K->496K(2560K)] 3408K->2346K(9728K), 0.0007793 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1238K->512K(2560K)] 7313K->6602K(9728K), 0.0005005 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 512K->496K(2560K)] 6602K->6602K(9728K)最後 新生代放到老年代,回收後沒變或者更大, 0.0003862 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 6106K->5014K(7168K)] 6602K->5014K(9728K), [Metaspace: 3439K->3439K(1056768K)], 0.0031555 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 5014K->5014K(8704K), 0.0003299 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 5014K->4912K(7168K)] 5014K->4912K(8704K), [Metaspace: 3439K->3439K(1056768K)], 0.0055557 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
遍歷次數為:16
Heap
 PSYoungGen      total 1536K, used 59K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 1024K, 5% used [0x00000000ffd00000,0x00000000ffd0ef18,0x00000000ffe00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 7168K, used 4912K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 68% used [0x00000000ff600000,0x00000000ffacc2f8,0x00000000ffd00000)
 Metaspace       used 3473K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 371K, capacity 388K, committed 512K, reserved 1048576K

堆空間分代思想

分代就是為了 最佳化 GC 效能,對朝生夕死的物件及逆行回收,就能騰出很大的空間

分代思想

assets/上内存与垃圾回收/image-20231219215221391.png

記憶體分配策略

Minor GC 存活--> age=1 -->每一次 Minor GC -->age+1 --> >設定值(15) -->晉升老年代

設定值 -XX:MaxTenuringThreshold 特殊: 若 s 區中相同年齡的物件大小總和大於 S 區空間的一半,則大於等於該年齡的物件可直接進入老年代

大物件直接分配到老年代

記憶體分配策略

assets/上内存与垃圾回收/image-20231219215520806.png assets/上内存与垃圾回收/image-20231219215920419.png

為物件分配記憶體: TLAB

TLAB 是 JVM 中的一個記憶體分配機制。它為每個執行緒分配一個小的記憶體區域,用於物件的快速分配,每個執行緒都在自己的 TLAB 中分配物件。這片區域是私有的,其他執行緒不能存取,這樣可以減少執行緒之間的競爭,提高記憶體分配的效率。但一旦物件分配完成並且有其他執行緒持有該物件的參考,其他執行緒是可以存取和使用這個物件的。

assets/上内存与垃圾回收/image-20231219220906590.png

TLAB 是 執行緒本地分配緩衝區, 每個執行緒都有一個自己的緩衝區,每個執行緒都有獨自的 TLAB 可以用來分配物件空間 (並不是所有物件都能分配到 TLAB[僅占 eden 1%], 但優先 TLAB),可以 減少執行緒之間的競爭,避免了部分執行緒安全問題

assets/上内存与垃圾回收/image-20240822175220083.png assets/上内存与垃圾回收/image-20240822175209687.png

TLAB (預設開啟)

assets/上内存与垃圾回收/image-20231219221147477.png assets/上内存与垃圾回收/image-20240113110531443.jpeg assets/上内存与垃圾回收/image-20231219221523405.png assets/上内存与垃圾回收/image-20240113110714798.jpeg

new 對應著在堆空間開闢新的空間,在 TLAB 開闢新的空間

堆空間的參數設定

java (oracle.com)


  測試堆空間常用的jvm參數:  
-XX:+PrintFlagsInitial : 查看所有的參數的預設初始值
-XX:+PrintFlagsFinal :查看所有的參數的最終值(可能會存在修改,不再是初始值)  
具體查看某個參數的指令:jps:查看當前執行中的行程  
                    jinfo -flag SurvivorRatio 行程id  
-Xms:初始堆空間記憶體 (預設為實體記憶體的1/64)  
-Xmx:最大堆空間記憶體(預設為實體記憶體的1/4)  
-Xmn:設定新生代的大小 (初始值及最大值)  
-XX:NewRatio:配置新生代與老年代在堆結構的占比 (預設2)
-XX:SurvivorRatio:設定新生代中Eden和S0/S1空間的比例(S0/S1空間如果過小過大就失去了Minor GC就失去了意義)
-XX:MaxTenuringThreshold:設定新生代垃圾的最大年齡  
-XX:+PrintGCDetails:輸出詳細的GC處理日誌  
  列印gc簡要資訊:① -XX:+PrintGC   ② -verbose:gc  
-XX:HandlePromotionFailure:是否設定空間分配擔保  
XX:HandlePromotionFailure

assets/上内存与垃圾回收/image-20231220163157501.png

堆是分配物件的唯一選擇嗎

堆是分配物件的唯一選擇嗎

assets/上内存与垃圾回收/image-20231220164352058.png

逃逸分析(6 後預設開啟的): 如果發現一個物件並沒有逃逸出方法的話,那麼就可能被優化成棧上分配

Close-DoEscapeAnalysis,不開意味著都在堆空間開闢空間 -XX: PrintEscapeAnalysis 查看逃逸分析的篩選結果

assets/上内存与垃圾回收/image-20231220165551880.png assets/上内存与垃圾回收/image-20231220164637479.png assets/上内存与垃圾回收/image-20231220164831795.png assets/上内存与垃圾回收/image-20231220164848573.png

物件在外部有調用的可能-->發生逃逸

如何快速的判斷是否發生了逃逸分析,大家就看new的物件實體是否有可能在方法外被調用

public EscapeAnalysis obj;  
  
/*  
方法返回EscapeAnalysis物件,發生逃逸  
 */  
public EscapeAnalysis getInstance(){  
    return obj == null? new EscapeAnalysis() : obj; 
}  
/*  
為成員屬性賦值,發生逃逸  
 */  
public void setObj(){  
    this.obj = new EscapeAnalysis();  
}  
//思考:如果當前的obj引用聲明為static What about it? Escape still occurs.  
  
/*  
The scope of the object is valid only within the current method, so no escape occurs.  
 */  
public void useEscapeAnalysis(){  
    EscapeAnalysis e = new EscapeAnalysis();  
}  
/*  
The value of the referenced member variable causes escape.  
 */  
public void useEscapeAnalysis1(){  
    EscapeAnalysis e = getInstance();  
    //getInstance().xxx()Escape also occurs.  
}
  • Conclusion

During development, if a local variable can be used, do not define it outside the method (do not declare it as a property).

Optimizations performed by the compiler using escape analysis

  1. Stack allocation
  2. Synchronization elision (lock elision)
  3. Scalar replacement of detached objects
Optimizations performed by the compiler using escape analysis

Optimization cannot be 100 percent complete, and some cases will always remain unoptimized on the stack.

assets/上内存与垃圾回收/image-20231220165803399.png assets/上内存与垃圾回收/image-20231220170937888.png assets/上内存与垃圾回收/image-20231220171311368.png

堆疊上分配測試

關閉逃逸分析:-DoEscapeAnalysis,未發生逃逸時,會有1千萬個User物件,且發生GC

開啟後,不會發生GC,只有10萬User物件


-Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails

public class StackAllocation {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            alloc();
        }
        // 查看執行時間
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");
        // 為了方便查看堆記憶體中物件個數, 執行緒sleep
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }

    private static void alloc() {
        User user = new User();
        //關閉逃逸分析:-DoEscapeAnalysis,未發生逃逸時,會有1千萬個User物件,and occurs GC
        //after enabling,does not occur GC,only 10 ten thousand User object
    }

    static class User {
    }
}

assets/上内存与垃圾回收/image-20231220171131773.png

純量替換

^ktyad4

純量替換

assets/上内存与垃圾回收/image-20231220173622255.png 未發生逃逸的 aggregate 經過 JIT 分析, 會被替換為 純量,不再需要建立物件,大幅減少堆積記憶體的占用 assets/上内存与垃圾回收/image-20231220173040492.png

assets/上内存与垃圾回收/image-20231220173054062.png assets/上内存与垃圾回收/image-20231220173934743.png assets/上内存与垃圾回收/image-20231220174005541.png

逃逸分析設定

assets/上内存与垃圾回收/image-20231220173634806.png

方法區 ( 元空間 )

方法區(堆積外空間)

方法區包括型別資訊、常數、靜態變數、執行時常數池、字串常數、編譯後的程式碼快取

位元組碼檔案中的常數池會被載入到方法區中,稱為執行時常數池

方法區 ( 元空間 )

assets/上内存与垃圾回收/image-20231225153936621.png assets/上内存与垃圾回收/image-20231222102122632.png

assets/上内存与垃圾回收/image-20231222102726838.png 指向物件型別資料的指標說明了是哪個類別 new 出來的,因此多型的資料型別要到執行時才會知道 ^9djjlm

方法區的理解

方法區可視為一塊獨立於 Java 堆積的記憶體空間,也可以固定大小或擴展,邏輯連續但實體不連續,是各個執行緒共用的空間

存在 OOM,可以不 GC,不去壓縮處理碎片問題

方法區的理解

assets/上内存与垃圾回收/image-20231222191204378.png assets/上内存与垃圾回收/image-20231222191239478.png assets/上内存与垃圾回收/image-20231222191306827.png

Hotspot 中方法区的演進

assets/上内存与垃圾回收/image-20231222191358646.png

設定方法區大小與 OOM

-XX:MetaspaceSize -XX:MaxMetaspaceSize (負一 沒有限制)

設定方法區大小與 OOM

assets/上内存与垃圾回收/image-20231222192234367.png assets/上内存与垃圾回收/image-20231222192426650.png

方法區的內部結構

方法區的內部結構

assets/上内存与垃圾回收/image-20231222193003995.png

assets/上内存与垃圾回收/image-20240113110831352.jpeg

assets/上内存与垃圾回收/image-20240113110927914.jpeg

各種資訊

assets/上内存与垃圾回收/image-20231222193039586.png

目前類別的完整資訊 assets/上内存与垃圾回收/image-20231222193538696.png assets/上内存与垃圾回收/image-20231222193112216.png assets/上内存与垃圾回收/image-20231222193837387.png assets/上内存与垃圾回收/image-20231222193200873.png assets/上内存与垃圾回收/image-20231222194450348.png

位元碼檔案中的常數池會被載入到方法區中,稱為執行時常數池

執行時常數池

assets/上内存与垃圾回收/image-20231222195749883.png class 檔案資訊 assets/上内存与垃圾回收/image-20231222195718834.png class 常數池

常數池是位元碼檔案中的 執行時常數池是方法區中的

常數池

存放編譯期間產生的各種字面量和符號引用 數量值,字串值,類別引用,欄位引用,方法引用,(字串, final 常數,基本資料型別,其他)和符號引用,載入後放到執行時常數池中

執行時常數池 是當 class 檔案被載入完成後,JVM 會將 class 常數池 裡的內容轉移到 執行時常數池 裡,在 class 常數池的符號引用有一部分是會被轉變為直接引用,比如說類別的靜態方法、私有方法、實例建構方法、父類別方法。這是因為這些方法不能被改寫其他版本,所以能在載入的時候就可以將符號引用轉變為直接引用,而其他的一些方法是在這個方法被第一次呼叫的時候才會將符號引用轉變為直接引用的

常數池

assets/上内存与垃圾回收/image-20240111123924519.png

assets/上内存与垃圾回收/image-20231224125930629.png assets/上内存与垃圾回收/image-20231224122933942.png

assets/上内存与垃圾回收/image-20231224122537993.png assets/上内存与垃圾回收/image-20240215184227199.png

assets/上内存与垃圾回收/image-20231224122609510.png assets/上内存与垃圾回收/image-20231224125745036.png

細節說明

assets/上内存与垃圾回收/image-20231224132838653.png assets/上内存与垃圾回收/image-20231224132039026.png assets/上内存与垃圾回收/image-20231224131837745.png assets/上内存与垃圾回收/image-20231224131844382.png assets/上内存与垃圾回收/image-20231224131907982.png 都算是符號引用,真正執行符號引用會轉換為直接引用

class 檔案資訊,class 檔案常數池,執行時常數池

assets/上内存与垃圾回收/image-20240111123737858.png

方法區的演進細節

assets/上内存与垃圾回收/image-20231224134048517.png

p97(沒聽)

assets/上内存与垃圾回收/image-20240113110738941.jpeg

assets/上内存与垃圾回收/image-20231224134207307.png assets/上内存与垃圾回收/image-20231224134233933.png

StrinaTable 為什麼調整

assets/上内存与垃圾回收/image-20231224134410950.png

靜態變數放在哪?

assets/上内存与垃圾回收/image-20231224152312262.png 三個物件都放在堆中,變數本身有的在堆中,有的在區域變數表中

第一個變數名: 堆空間,雖然規定了要放在方法區中,但 JDK 7 後虛擬機器自己選擇放到了堆空間中

第二個變數名: 是成員變數,放到了堆空間中

第三個變數名: 堆疊框架中的區域變數表 assets/上内存与垃圾回收/image-20231224152414439.png

方法區的垃圾回收

方法區的垃圾回收通常為執行時常數池中廢棄的常數和不再使用的型別

assets/上内存与垃圾回收/image-20231225152758231.png assets/上内存与垃圾回收/image-20231225153611367.png

總結

物件實例化,記憶體布局與存取定位

assets/上内存与垃圾回收/image-20240215182330370.png

assets/上内存与垃圾回收/image-20240113111203454.png

物件的實例化

位元碼角度

assets/上内存与垃圾回收/image-20231226155441196.png assets/上内存与垃圾回收/image-20231226155437125.png 判斷類別是否載入 new 方法區把類別載入,堆開闢空間,此時類別已經確定

物件建立執行步驟 ^2kosms

物件建立執行步驟

① 載入類別元資訊 ② 為物件分配記憶體(和清除演算法有關) ③ 處理並行問題 ④ 屬性的預設初始化(零值初始化)
⑤ 設定物件頭的資訊 ⑥ 屬性的顯式初始化、程式碼區塊中初始化、建構子中初始化

assets/上内存与垃圾回收/image-20240113111214651.png

  1. assets/上内存与垃圾回收/image-20231226155918909.png
  2. assets/上内存与垃圾回收/image-20231226160849455.png 規整,指標碰撞:有個指標指向空閒和使用的分界處 assets/上内存与垃圾回收/image-20231226160934854.png 不規整:空閒列表法 assets/上内存与垃圾回收/image-20231226161217061.png assets/上内存与垃圾回收/image-20231226161401890.png assets/上内存与垃圾回收/image-20231226162022486.png assets/上内存与垃圾回收/image-20231226162821183.png id,name,account 都是在 init 中執行的 Java SE/4.面对对象编程/3.面向对象下/4.代码块#1 3 程序中成员变量赋值的执行顺序

^2wz2dk

物件的記憶體布局

assets/上内存与垃圾回收/image-20240113111239156.jpeg

java

public class Customer{  
    int id = 1001;  
    String name;  
    Account acct;  
    {  
        name = "匿名客户";  
    }  
    public Customer(){  
        acct = new Account();  
    }  
  
}
class Account{  
  
}

public class CustomerTest {  
    public static void main(String[] args) {  
        Customer cust = new Customer();  
    }  
}

assets/上内存与垃圾回收/image-20240113111354641.jpeg

物件的存取定位

大多數現代 JVM(例如 HotSpot JVM)預設使用直接指標(Direct Pointer)存取方式,因為它提供了更高的存取效率

  • 使用-XX:+UseCompressedOops(預設啟用)表示使用壓縮指標(這通常意味著直接指標)。
  • 使用-XX:-UseCompressedOops表示停用壓縮指標。

這些參數主要是針對指標壓縮的設定,但是不同的 JVM 實作可能會有不同的參數來選擇物件存取方式。如果需要變更具體的物件存取方式,可能需要查閱特定 JVM 實作的說明文件。

assets/上内存与垃圾回收/image-20231227105726223.png

區域變數表 中儲存 控制代碼存取 和 直接存取 的值

區域變數表

儲存 : 控制代碼池中控制代碼物件的參考 儲存 : 物件的直接參考

控制代碼存取

控制代碼池: 儲存到物件實例和物件型別的指標

assets/上内存与垃圾回收/image-20240113111320035.jpeg

好處:物件移動時,到物件型別的資料的指標不需要修改(標記整理演算法,新生區 s 交換需要移動)

assets/上内存与垃圾回收/image-20240113111335422.jpeg

直接指標

assets/上内存与垃圾回收/image-20240113111326967.jpeg

效率比上面的高

直接記憶體

assets/上内存与垃圾回收/image-20231227110646867.png assets/上内存与垃圾回收/image-20231227110736030.png

簡介

assets/上内存与垃圾回收/image-20231227113704777.png assets/上内存与垃圾回收/image-20231227113712266.png

assets/上内存与垃圾回收/image-20231227114712584.png 由於看不到本地記憶體,但也會出現 OutOfMemoryError,出現類似錯誤需要考慮是否可能出現這中錯誤

assets/上内存与垃圾回收/image-20231227115519204.png

執行引擎

Java 核心組成部分之一,將位元組碼指令解釋/後端編譯成機器指令

虛擬機的執行引擎是由軟體自行實作的,因此能夠執行不被硬體直接支援的指令集格式

執行引擎的輸入輸出都是位元組串流,但輸入的是位元組碼二進位串流,處理過程是位元組碼解譯指令的等效過程,輸出的是執行結果

執行引擎

assets/上内存与垃圾回收/image-20240113111440375.png assets/上内存与垃圾回收/image-20231227115927857.png assets/上内存与垃圾回收/image-20231227120102160.png assets/上内存与垃圾回收/image-20231227120406009.png assets/上内存与垃圾回收/image-20231227121113991.png

Java 程式碼編譯和執行過程

assets/上内存与垃圾回收/image-20240113111456643.jpeg

綠色代表解譯器執行過程 藍色代表編譯器執行過程

assets/上内存与垃圾回收/image-20231227121553263.png assets/上内存与垃圾回收/image-20231227121603197.png assets/上内存与垃圾回收/image-20240103111144880.png assets/上内存与垃圾回收/image-20240103111748302.png

例圖

assets/上内存与垃圾回收/image-20240113111704707.jpeg assets/上内存与垃圾回收/image-20240113111709694.jpeg assets/上内存与垃圾回收/image-20240113111721325.jpeg

簡介
  • 機器碼 assets/上内存与垃圾回收/image-20240103112058952.png assets/上内存与垃圾回收/image-20240103112106935.png assets/上内存与垃圾回收/image-20240103112137659.png 機器碼-->指令-->指令集-->組合語言---> 高階語言

assets/上内存与垃圾回收/image-20240113111554942.jpeg

位元組碼檔案

assets/上内存与垃圾回收/image-20240103112333300.png assets/上内存与垃圾回收/image-20240103112348012.png assets/上内存与垃圾回收/image-20240103112401950.png

JIT 編譯器

assets/上内存与垃圾回收/image-20240103113304223.png

解譯器對比 JIT 編譯器

assets/上内存与垃圾回收/image-20240103113603215.png HotSpot 會自己選擇使用解譯器還是使用編譯器 assets/上内存与垃圾回收/image-20240103114730422.png Jit 將程式碼編譯且快取起來了,比解譯器快,而解譯器回應速度快 assets/上内存与垃圾回收/image-20240103114634681.png assets/上内存与垃圾回收/image-20240103114639767.png

熱機冷機案例

由於還沒有進行熱點程式碼統計和 JIT 動態編譯

assets/上内存与垃圾回收/image-20240103115043472.png

assets/上内存与垃圾回收/image-20240103115605681.png

熱點程式碼

assets/上内存与垃圾回收/image-20240103115646235.png assets/上内存与垃圾回收/image-20240103115653023.png assets/上内存与垃圾回收/image-20240103120125926.png 超過這個閾值的才會被 cache assets/上内存与垃圾回收/image-20240103120306451.png

  • 熱度衰減 assets/上内存与垃圾回收/image-20240103120356743.png

  • HotSpot VM 中 JIT 分類

assets/上内存与垃圾回收/image-20240103122534713.png assets/上内存与垃圾回收/image-20240103122606560.png assets/上内存与垃圾回收/image-20240103122703538.png assets/上内存与垃圾回收/image-20240103122809938.png

  • AOT 編譯器 (-->.so) (linux64 支援)

assets/上内存与垃圾回收/image-20240103123037872.png assets/上内存与垃圾回收/image-20240103123054919.png assets/上内存与垃圾回收/image-20240103123454929.png

assets/上内存与垃圾回收/image-20240113111536267.jpeg assets/上内存与垃圾回收/image-20240113111531013.jpeg

本地方法庫與介面

簡介

assets/上内存与垃圾回收/image-20240113111642471.jpeg

assets/上内存与垃圾回收/image-20231212211357802.png

assets/上内存与垃圾回收/image-20231215183030134.png

為什麼需要 Native Method

簡介

assets/上内存与垃圾回收/image-20231215184113059.png assets/上内存与垃圾回收/image-20231215185144611.png

assets/上内存与垃圾回收/image-20231215185241745.png

StringTable

垃圾回收機制

什麼是垃圾

assets/上内存与垃圾回收/image-20240104104012841.png assets/上内存与垃圾回收/image-20240104110351390.png

assets/上内存与垃圾回收/image-20240104111016605.png

早期了解

assets/上内存与垃圾回收/image-20240104111837359.png assets/上内存与垃圾回收/image-20240104111848879.png

Java 垃圾回收機制

垃圾回收相關演算法

垃圾標記階段

引用計數器演算法(不使用) / 可達性分析演算法 , 可區分已經死亡需要釋放的物件

標記階段:引用計數器演算法

  • 引用計數器演算法 (不使用)
引用計數器演算法

assets/上内存与垃圾回收/image-20240104174825424.png 存在循環引用問題

assets/上内存与垃圾回收/image-20240104175218710.png 導致 3 個沒有被 GC, 其實已經不需要了 assets/上内存与垃圾回收/image-20240106115252023.png

標記階段:可達性分析演算法

區域變數表中的變數是垃圾回收的根節點 (只要被區域變數表中直接或間接引用的物件都不會被回收)

透過可達性分析演算法 , 根據 根物件集合 (GC Roots) 為初始起點,搜尋被根物件集合所連接的目標物件是否可達,搜尋過的路徑被稱為 引用鏈,如果物件沒有被任何引用鏈相連接,則是不可達的, 就意味物件死亡 (也有可能之後復活)

JVM 與上記憶體與垃圾回收 > ^0oij7m

  • 可以構成 根物件集合 (GC Roots) 的
    1. 虛擬機棧引用的物件 Object obj1 = new Object();
    2. 本地方法棧內本地方法引用的物件 public native void nativeMethod();
    3. 方法區中類別靜態屬性引用物件 public static Object obj2 = new Object();
    4. 方法區常數引用的物件 public static final String STR = "Hello";
    5. 執行時常數池中引用的物件 ``
可達性分析演算法

assets/上内存与垃圾回收/image-20240106115919616.png assets/上内存与垃圾回收/image-20240106115956811.png assets/上内存与垃圾回收/image-20240106120505337.png assets/上内存与垃圾回收/image-20240106120532333.png assets/上内存与垃圾回收/image-20240106120709952.png assets/上内存与垃圾回收/image-20240106120843999.png 一致性 : 不在物件引用動態變化進行判斷,必須 Stop the world

物件的 finalization 機制

GC 回收會自動呼叫 finalize 方法

object 類別中的 finalize 是空白,子類別重寫了這個方法,如 close(),永遠不要主動呼叫 finalize 方法

finalize 沒有被重寫則會直接被判定為不可觸及

如果被重寫了也只能呼叫一次,會將該類別插入到一個 F-Queue 佇列中,有虛擬機呼叫 finalize 方法, 呼叫後的物件沒有復活就會變成不可觸及狀態 (被標記為垃圾),永遠無法復活 (具體流程為圖四) 或物件沒有重寫 finalize 方法呼叫 finalize 也會被視為不可觸及

物件的 finalization 機制

assets/上内存与垃圾回收/image-20240106122225079.png assets/上内存与垃圾回收/image-20240106122233893.png assets/上内存与垃圾回收/image-20240106122704204.png

  • 兩次標記 assets/上内存与垃圾回收/image-20240106123229617.png

^0oij7m

GC 只會被呼叫一次,(使用 finalize 只能復活一次,第二次不會復活)

復活場景

手動重寫的 finalize 方法,與引用鏈上的任意一個物件建立了聯繫

public class CanReliveObj {  
    public static CanReliveObj obj;//類變數,屬於 GC Root  
  
    //此方法只能被呼叫一次  
    @Override  
    protected void finalize() throws Throwable {  
        System.out.println("调用当前类重写的finalize()方法");  
        obj = this;//當前待回收的物件在finalize()方法中與引用鏈上的一個物件obj建立了聯繫  
    }  
  
  
    public static void main(String[] args) {  
        try {  
            obj = new CanReliveObj();  
            // 物件第一次成功拯救自己  
            obj = null;  
            System.gc();//呼叫垃圾回收器,呼叫了重寫的finalize, obj = this,建立了聯繫  
            System.out.println("第1次 gc");  
            // 因為Finalizer執行緒優先級很低,暫停2秒,以等待它  
            Thread.sleep(2000);  
            if (obj == null) {  
                System.out.println("obj is dead");  
            } else {  
                System.out.println("obj is still alive");  
            }  
  
  
            System.out.println("第2次 gc");  
            // 下面這段程式碼與上面的完全相同,但是這次自救卻失敗了  
            obj = null;  
            System.gc();  
            // 因為Finalizer執行緒優先級很低,暫停2秒,以等待它  
            Thread.sleep(2000);  
            if (obj == null) {  
                System.out.println("obj is dead");  
            } else {  
                System.out.println("obj is still alive");  
            }  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}
  • MAT 與 JProfiler 的 GC Roots 溯源

assets/上内存与垃圾回收/image-20240106144818406.png

清除階段:標記-清除演算法

空間滿後進入 STW ,進入標記和清除兩個階段

標記清除兩個環節

標記:標記可達物件(非垃圾物件) 清除:對堆記憶體從頭到尾進行走訪,沒有被標記的會被清除

效率不高,產生碎片 需要維護空閒列表

簡介

assets/上内存与垃圾回收/image-20240106151904343.png

綠色是可達到的物件,黑色是發現沒有被標記的

assets/上内存与垃圾回收/image-20240113111737705.jpeg

stop the world

assets/上内存与垃圾回收/image-20240106153137884.png

空閒列表(放入新的如果不夠可以放入老年代空閒空間)

assets/上内存与垃圾回收/image-20240106153215613.png

清除階段:複製演算法

複製演算法適合存活物件少,垃圾多時複製成本過高

將活著的記憶體空間分成了兩塊,每次只使用其中一塊(需要兩倍空間) 高效沒有碎片,但是浪費空間,GC 需要維護 region 之間物件引用關係

因為記憶體規則,建立物件使用指標碰撞就可以

刪除並不是真正的刪除

複製演算法

assets/上内存与垃圾回收/image-20240106153719408.png assets/上内存与垃圾回收/image-20240113111822805.jpeg

assets/上内存与垃圾回收/image-20240106160658616.png 如果大部分都是存活物件時效率會很差,但一般新生代一次都可以回收 70% 到 99% 的記憶體空間

assets/上内存与垃圾回收/image-20240106160606621.png

清除階段:標記-壓縮演算法(標記-清除-壓縮)

標記-壓縮 演算法:先標記清除,然後把物件 移動 到一端(移動過程會 STW),建立的新物件需要 指標碰撞

而 標記-清除 沒有移動,只是清除,需要維護空閒列表

指標碰撞:只需要修改指標的偏移量就可以將物件分配到一個空閒記憶體位置上

標記-壓縮演算法

assets/上内存与垃圾回收/image-20240106160923391.png assets/上内存与垃圾回收/image-20240106161001169.png assets/上内存与垃圾回收/image-20240113111836561.jpeg assets/上内存与垃圾回收/image-20240106161301481.png assets/上内存与垃圾回收/image-20240106161703135.png 指標碰撞 assets/上内存与垃圾回收/image-20240106161728958.png assets/上内存与垃圾回收/image-20240106161750996.png

分代收集演算法

不同生命週期的物件採用不同的收集方式,以便提高收集效率

Young Generation: Copying algorithm. The efficiency of the copying algorithm only relates to object size, and the Young Generation has few surviving objects, so few objects need copying.

Old Generation: Mark Sweep, Mark Compact, or a combination of both.

Mark Phase consumption is number of surviving objects directly proportional directly proportional Sweep Phase consumption is size of the managed region positively correlated positively correlated Compact Phase consumption is surviving object data directly proportional directly proportional

assets/上内存与垃圾回收/image-20240221150625262.png

Generational collection algorithm

assets/上内存与垃圾回收/image-20240106162011149.png assets/上内存与垃圾回收/image-20240106162513318.png assets/上内存与垃圾回收/image-20240106162553057.png assets/上内存与垃圾回收/image-20240106164030824.png

Incremental collection algorithm, partition algorithm

Incremental collection algorithm

Because STW it causes long pauses, the garbage collection thread and the application thread can execute alternately, and each garbage collection reclaims a small region.

Partition algorithm

divides the entire space into several small blocks.

Introduction
  • Incremental collection algorithm assets/上内存与垃圾回收/image-20240106172539282.png assets/上内存与垃圾回收/image-20240106172829471.png

  • Partition algorithm assets/上内存与垃圾回收/image-20240106173218314.png assets/上内存与垃圾回收/image-20240106173522985.png assets/上内存与垃圾回收/image-20240106173622039.png assets/上内存与垃圾回收/image-20240113111851852.jpeg

Concepts related to garbage collection

System.gc() understanding of

andRuntime.getRuntime().gc();has the same effect trigger Full GC, only reminds the JVM to perform GC, and does not guarantee that GC executes.

System.runFinalization();//force call the referenced object finalize()method

Introduction

assets/上内存与垃圾回收/image-20240106193203996.png

GC case
//will not GC
public void localvarGC1() {  
    byte[] buffer = new byte[10 * 1024 * 1024];//10MB  
    System.gc();  
}  

  //buffer be collected
public void localvarGC2() {  
    byte[] buffer = new byte[10 * 1024 * 1024];  
    buffer = null;  
    System.gc();  
}  

//buffer was not collected,because no object needs to occupy buffer the location of slot slot, does not need to be collected, but the bytecode will not display
![[assets/上内存与垃圾回收/image-20240106195523072.png]]
public void localvarGC3() {  
    {  
        byte[] buffer = new byte[10 * 1024 * 1024];  
    }  
    System.gc();  
}  

//at this time buffer will be collected,value will occupy the previous buffer of the slot slot, which causes the original reference to no longer exist, and the original data is GC collected
public void localvarGC4() {  
    {  
        byte[] buffer = new byte[10 * 1024 * 1024];  
    }  
    int value = 10;  
    System.gc();  
}  


public void localvarGC5() {  
    localvarGC1();  
    System.gc();  
}  

// after the method call is finished, then GC can be collected
public static void main(String[] args) {  
    LocalVarGC local = new LocalVarGC();  
    local.localvarGC5();  
}

Memory overflow and memory leak

Memory overflow OutOfMemoryError

There is not enough free space, andthe garbage collectorcannot provide more memory.

Memory overflow

assets/上内存与垃圾回收/image-20240106200751527.png assets/上内存与垃圾回收/image-20240106201216836.png

Memory leak (GC cannot collect)

A memory leak may lead to memory overflow.

Memory leak caused by design that cannot clean up or forgetting to close .close causing the memory leak

記憶體洩漏

assets/上内存与垃圾回收/image-20240106203539343.png

assets/上内存与垃圾回收/image-20240106204445588.png 忘記斷開一個參考導致存在記憶體洩漏

assets/上内存与垃圾回收/image-20240106212636767.png

Stop The World

進行垃圾回收的過程中,會涉及物件的移動。為了保證物件參考更新的正確性,必須暫停所有的使用者執行緒

STW 過程中整個引用程式的執行緒都會被暫停 任何垃圾回收器都會有這個操作

assets/上内存与垃圾回收/image-20240106213128905.png assets/上内存与垃圾回收/image-20240106213443120.png

垃圾回收的平行與並行

平行並行複習
  • 平行 assets/上内存与垃圾回收/image-20240106214704076.png

  • 並行 assets/上内存与垃圾回收/image-20240106214714260.png assets/上内存与垃圾回收/image-20240106215025445.png

垃圾回收器的平行並行

assets/上内存与垃圾回收/image-20240106215325770.png assets/上内存与垃圾回收/image-20240106215602587.png

OopMap

OopMap 是 HotSpot 虛擬機中的一種資料結構,用於記錄物件的配置資訊。一旦類別載入完成,HotSpot 會根據物件的記憶體配置,計算並記錄在物件記憶體中的哪些偏移量上儲存著什麼類型的資料。同時,在即時編譯過程中,HotSpot 也會在特定位置產生 OopMap,用於記錄堆疊上和暫存器中的參考位置。

OopMap 使得 JVM 可以實現精確式 GC assets/上内存与垃圾回收/PixPin_2025-02-26_16-15-38.png

assets/上内存与垃圾回收/PixPin_2025-02-26_16-15-32.png

安全點與安全區域

安全點與安全區域是為了保證 GC 的效率效能

並不是隨時可以停下來進行 GC, 只有特定位置停下來進行 GC

根據長執行的特徵作為標準

而在安全區域 (程式碼片段)內則都可以進行 GC

如果堆積空間滿了,還沒到安全點,JVM 可能會觸發一些緊急的垃圾回收操作,以騰出一些空間。這通常被稱為「GC 主動觸發」或「GC 主動處理」(Garbage Collection induced by Application Threads)

簡介

assets/上内存与垃圾回收/image-20240106215725990.png assets/上内存与垃圾回收/image-20240106220142390.png assets/上内存与垃圾回收/image-20240106220301890.png assets/上内存与垃圾回收/image-20240106220444863.png

再談參考

4 種參考強度依次減弱,都是可達的

我們為了對物件回收的優先順序進行區分,區分出哪些要優先回收,對應用類型進行區分

再談參考

assets/上内存与垃圾回收/image-20240106220745801.png assets/上内存与垃圾回收/image-20240106221259542.png

強參考 (主)

寧可拋出例外也不回收

強參考

assets/上内存与垃圾回收/image-20240106221654475.png assets/上内存与垃圾回收/image-20240106221911842.png

軟參考 - 不足即回收

assets/上内存与垃圾回收/image-20240619102832637.png

軟參考 - 不足即回收,回收後空間還不足就會爆 OOM 例外

當爆 OOM 時和軟參考無關 爆 OOM 時軟參考已經被回收

並不是 GC 後軟參考就不在,而是滿時前的 GC 才會清除,記憶體充足時不會回收

記憶體充足不會回收,不足才會回收

軟參考

assets/上内存与垃圾回收/image-20240106222057739.png assets/上内存与垃圾回收/image-20240106222409114.png assets/上内存与垃圾回收/image-20240106222458323.png 此時有一個軟參考,一個強參考

弱參考 - GC發現即回收

只要 GC 發現了就回收,無論記憶體充足與否

弱參考

assets/上内存与垃圾回收/image-20240106223414033.png assets/上内存与垃圾回收/image-20240106223655512.png assets/上内存与垃圾回收/image-20240324175109113.png

虛參考 - 物件回收追蹤

虛參考可以 追蹤 物件的回收時間,可以將一些資源釋放的操作放置在虛參考種執行和記錄, 只是為了在物件被垃圾回收時收到通知。

虛參考不能單獨使用,它必須與參考佇列(ReferenceQueue)聯合使用。

GC 具有不確定性,只是通知 JVM, 且 GC 只會被呼叫一次,(使用 finalize 只能復活一次)

設定虛參考關聯的唯一目的,就是在這個物件被收集器回收的時候收到一個系統通知或者後續新增進一步的處理。

java
public class PhantomReferenceExample {
    public static void main(String[] args) throws InterruptedException {
        // 建立一個參考佇列
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();

        // 建立一個物件並將其包裝在虛參考中
        Object obj = new Object();
        PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue);

        // Clear strong references and allow the object to be garbage collected
        obj = null;

        // Force garbage collection to run
        System.gc();

        // Wait for a period of time to ensure the garbage collector runs
        Thread.sleep(1000);

        // Check whether the reference queue contains a phantom reference
        if (referenceQueue.poll() != null) {
            System.out.println("对象已被垃圾回收");
        } else {
            System.out.println("对象尚未被垃圾回收");
        }
    }
}
Phantom Reference

assets/上内存与垃圾回收/image-20240106224209530.png assets/上内存与垃圾回收/image-20240106224552533.png assets/上内存与垃圾回收/image-20240106225105443.png assets/上内存与垃圾回收/image-20240106225420979.png

java

 public class PhantomReferenceTest {  
    public static PhantomReferenceTest obj;//Declaration of the current class object  
    static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//Reference queue  
  
    public static class CheckRefQueue extends Thread {  
        @Override  
        public void run() {  
            while (true) {  
                if (phantomQueue != null) {  
                    PhantomReference<PhantomReferenceTest> objt = null;  
                    try {  
                        objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    if (objt != null) {  
                        System.out.println("追踪垃圾回收过程:PhantomReferenceTest实例被GC了");  
                    }  
                }  
            }  
        }  
    }  
  
    @Override  
    protected void finalize() throws Throwable { //finalize()The method can be called only once!  
        super.finalize();  
        System.out.println("调用当前类的finalize()方法");  
        obj = this;  
    }  
  
    public static void main(String[] args) {  
        Thread t = new CheckRefQueue();  
        t.setDaemon(true);//Set as a daemon thread: when the program has no non daemon threads, the daemon thread also finishes running.  
        t.start();  
  
        phantomQueue = new ReferenceQueue<PhantomReferenceTest>();  
        obj = new PhantomReferenceTest();  
        //Constructed the PhantomReferenceTest phantom reference of the object and specified the reference queue  
        PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);  
  
        try {  
            //The object in the phantom reference cannot be obtained  
            System.out.println(phantomRef.get());  
  
            //Remove the strong reference  
            obj = null;  
            //Performed for the first time GC,Because the object can be resurrected,GC the object cannot be collected  
            System.gc();  
            Thread.sleep(1000);  
            if (obj == null) {  
                System.out.println("obj 是 null");  
            } else {  
                System.out.println("obj 可用");  
            }  
            System.out.println("第 2 次 gc");  
            obj = null;  
            System.gc(); //Once the obj object is collected, this phantom reference will be placed into the reference queue.  
            Thread.sleep(1000);  
            if (obj == null) {  
                System.out.println("obj 是 null");  
            } else {  
                System.out.println("obj 可用");  
            }  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}

Finalizer Reference

assets/上内存与垃圾回收/image-20240106225844549.png

Garbage Collector

The garbage collector is not specified in great detail in the standard, so it can be implemented by different vendors and different versions of the JVM, and can be divided into many types

GC Classification and Performance Metrics

GC Classification
  • By thread

assets/上内存与垃圾回收/image-20240107111345075.png assets/上内存与垃圾回收/image-20240107111534010.png

  • By working mode assets/上内存与垃圾回收/image-20240107111607780.png

assets/上内存与垃圾回收/image-20240107111626297.png

PerformanceKey pointsThroughput(runtime ratio to code runtime) andpause time

Low latency: the proportion of time spent running user code to total runtime (total runtime: program runtime plus memory reclamation time) Throughput: background computation

Current standard: prioritize maximum throughput while reducing pause time

and Performance Metrics

assets/上内存与垃圾回收/image-20240107112358409.png assets/上内存与垃圾回收/image-20240107112856821.png assets/上内存与垃圾回收/image-20240107113235799.png assets/上内存与垃圾回收/image-20240107113316748.png assets/上内存与垃圾回收/image-20240107113452479.png assets/上内存与垃圾回收/image-20240107113557964.png assets/上内存与垃圾回收/image-20240107113617823.png

Overview of Different Garbage Collectors

assets/上内存与垃圾回收/image-20240107114154571.png

7 classic garbage collectors

assets/上内存与垃圾回收/image-20240107114348906.png assets/上内存与垃圾回收/image-20240107114356218.png JDK14 relationship diagram assets/上内存与垃圾回收/image-20240107114647604.png

Parallel Scavenge has a different underlying implementation and cannot be paired with CMS GC

assets/上内存与垃圾回收/image-20240107115532882.png

  • View the default garbage collector

-XX:+PrintCommadLineFlags View command line related parameters (including the garbage collector in use)

Command line instructions jinfo -flag Related garbage collection parameters process ID

Serial Collector: Serial Collection (overview)

Serial Collector: Serial Collection

assets/上内存与垃圾回收/image-20240107120514294.png assets/上内存与垃圾回收/image-20240107121618315.png assets/上内存与垃圾回收/image-20240107123433264.png assets/上内存与垃圾回收/image-20240107123524470.png

ParNew Collector: Parallel Collection (out of favor)

-XX:+UseParNewGC Manually specify ParNew -XX:ParalleGCThreads Limit the number of threads

ParNew Collector: Parallel Collection

assets/上内存与垃圾回收/image-20240107123556660.png assets/上内存与垃圾回收/image-20240107124508693.png assets/上内存与垃圾回收/image-20240107124544090.png

Parallel Collector: Throughput Priority (JDK 8 default)

Low latency: requires user interaction Throughput: background computation

Parallel Scavenge

assets/上内存与垃圾回收/image-20240107125244234.png assets/上内存与垃圾回收/image-20240107125707579.png assets/上内存与垃圾回收/image-20240107125757591.png assets/上内存与垃圾回收/image-20240107130227803.png

-XX:+UserParallelGC -XX:+UserParal1elOldGC -XX:ParallelGCThreads -XX:MaxGCPauseMillis -XX:GCTimeRatio -XX:+UseAdaptiveSizePolicy

Configuration

assets/上内存与垃圾回收/image-20240107134213731.png assets/上内存与垃圾回收/image-20240107134615310.png assets/上内存与垃圾回收/image-20240107135236718.png

CMS Collector: Low Latency (concurrent)

CMS Collector: Low Latency

assets/上内存与垃圾回收/image-20240107204221395.png assets/上内存与垃圾回收/image-20240107210455027.png assets/上内存与垃圾回收/image-20240107210547123.png assets/上内存与垃圾回收/image-20240107210718077.png assets/上内存与垃圾回收/image-20240107210826841.png assets/上内存与垃圾回收/image-20240107211106515.png

assets/上内存与垃圾回收/image-20240107211307150.png To ensure that user objects can continue to execute, the address of the object cannot be changed, so compact cannot be used assets/上内存与垃圾回收/image-20240107211503158.png

Settings

assets/上内存与垃圾回收/image-20240107211857319.png assets/上内存与垃圾回收/image-20240107211916000.png

Summary

assets/上内存与垃圾回收/image-20240107212729111.png assets/上内存与垃圾回收/image-20240107212932108.png

G1 Collector

G1 收集器則不需要區分新生代和老年代,

G1 採用分區演算法

將整個堆積記憶體劃分成多個大小相等的 Region 區域, 並根據 Region 中的物件存活時間和佔用空間來決定回收優先順序。

G1 回收器

assets/上内存与垃圾回收/image-20240107213106956.png assets/上内存与垃圾回收/image-20240107213430144.png assets/上内存与垃圾回收/image-20240107213713084.png

  • 特點
  1. region
  2. 可預測停頓模型 soft real-time
G1 的特點優勢

assets/上内存与垃圾回收/image-20240107213802516.png assets/上内存与垃圾回收/image-20240113111929875.jpeg assets/上内存与垃圾回收/image-20240107214232744.png assets/上内存与垃圾回收/image-20240107214238316.png assets/上内存与垃圾回收/image-20240107214453277.png assets/上内存与垃圾回收/image-20240107214650285.png

G1 使用壓縮技術來整理堆積空間。在垃圾回收過程中,G1 會將存活物件移動到堆積中的其他區域,並且盡量減少記憶體碎片化,從而實現記憶體的壓縮。這一步驟類似於標記-清除演算法中的壓縮過程

assets/上内存与垃圾回收/image-20240107215210464.png

G1 參數設定

assets/上内存与垃圾回收/image-20240107220924224.png assets/上内存与垃圾回收/image-20240107220945597.png assets/上内存与垃圾回收/image-20240107220229455.png

適用場景

assets/上内存与垃圾回收/image-20240107221120825.png

Region

一個 region 只能是一個角色, 清空後可以轉換角色, 大於 1.5倍region 的物件會放到 humongous (避免短期大物件放到老年代堆垃圾收集器造成影響,確保老年代都是長期物件)

Region
  • 化整為零 assets/上内存与垃圾回收/image-20240107221453673.png G1 垃圾回收器, 新生代老年代在邏輯上不再是連續的了 assets/上内存与垃圾回收/image-20240107221618124.png assets/上内存与垃圾回收/image-20240107221947821.png
  • region 內部細節 assets/上内存与垃圾回收/image-20240107222255113.png 指標在allocated/unallocate分界處 assets/上内存与垃圾回收/image-20240107222203540.png

G1 回收過程

混合回收 :

由於低延遲: 只挑選價值比較高的進行回收

G1 回收過程

assets/上内存与垃圾回收/image-20240107224821043.png assets/上内存与垃圾回收/image-20240107230936029.png assets/上内存与垃圾回收/image-20240107230957641.png

給每個 region 配置了一個 Rset,記錄了該 region 被哪個 region 指向 ^atdrli

Remembered Set

解決了一個物件被不同區域引用的問題 回收年輕代也不得不同時掃描老年代,因為老年代的物件可能引用了新生代的物件 Remembered Set 避免了全局掃描, 而回收老年代則不用擔心年輕代引用了老年代

assets/上内存与垃圾回收/image-20240108185035385.png 如果引用的物件是同一個 region 內的就會不記錄 assets/上内存与垃圾回收/image-20240108185506711.png

  1. 年輕代 GC
    1. 掃描根
    2. 更新 Rset : 會根據 dirty card queue 更新 Rset (dirty card queue 會在引用賦值語句前後保存物件的引用資訊) (由於 Rset 需要執行緒同步, 沒有直接記錄到 Rset 中, 而是記錄到 dirty car queue 中)
    3. 處理 Rset
    4. 複製物件
    5. 處理引用
  2. 併發標記過程
    1. 初始標記階段
    2. 根區域掃描
    3. 併發標記 : 只會回收一部分,判斷活性看值不值得回收
    4. 再次標記 : 修正上一次標記結果 snap-at-the-beginning
    5. 獨佔清理: 計算存活物件和 GC 回收比例,沒有真正清理
    6. 併發清理
回收具體過程 (了解)

assets/上内存与垃圾回收/image-20240108192302862.png assets/上内存与垃圾回收/image-20240108192855976.png

  1. 年輕代 GC assets/上内存与垃圾回收/image-20240108193020452.png

assets/上内存与垃圾回收/image-20240108193224936.png

  1. 併發標記過程

assets/上内存与垃圾回收/image-20240108194440316.png

  1. 混合回收

assets/上内存与垃圾回收/image-20240111183029630.png 複製演算法. 避免了碎片

assets/上内存与垃圾回收/image-20240111183654633.png

  1. Full GC(可選過程四)

assets/上内存与垃圾回收/image-20240111183724528.png

補充

assets/上内存与垃圾回收/image-20240111183953512.png

assets/上内存与垃圾回收/image-20240111184202354.png

總結

總結

assets/上内存与垃圾回收/image-20240113111940060.jpeg assets/上内存与垃圾回收/image-20240113111946541.jpeg

assets/上内存与垃圾回收/image-20240111184228347.png

assets/上内存与垃圾回收/image-20240111184953944.png assets/上内存与垃圾回收/image-20240111185012915.png assets/上内存与垃圾回收/image-20240111185022778.png assets/上内存与垃圾回收/image-20240111185154245.png assets/上内存与垃圾回收/image-20240111185203882.png

assets/上内存与垃圾回收/image-20240112224707096.png

assets/上内存与垃圾回收/image-20240112224714647.png assets/上内存与垃圾回收/image-20240113111953121.jpeg

assets/上内存与垃圾回收/image-20240112224740217.png assets/上内存与垃圾回收/image-20240112224748537.png

assets/上内存与垃圾回收/image-20240112224757251.png

ZGC assets/上内存与垃圾回收/image-20240112230757316.png

assets/上内存与垃圾回收/image-20240112230811995.png assets/上内存与垃圾回收/image-20240112230831238.png assets/上内存与垃圾回收/image-20240112230841946.png assets/上内存与垃圾回收/image-20240112230910305.png assets/上内存与垃圾回收/image-20240112230920166.png

GC 日誌分析

GC 日誌分析

參數列表 assets/上内存与垃圾回收/image-20240111214121445.png assets/上内存与垃圾回收/image-20240111214142914.png assets/上内存与垃圾回收/image-20240111214157452.png assets/上内存与垃圾回收/image-20240111214203637.png assets/上内存与垃圾回收/image-20240111221454204.png Young GC assets/上内存与垃圾回收/image-20240111221506348.png Full GC assets/上内存与垃圾回收/image-20240112221937330.png

案例

assets/上内存与垃圾回收/image-20240112222642478.png 物件後不下後先進行一次GC,發現s區放不下年輕代中的內容,直接到老年代,4MB的大物件在年輕代能放下了 assets/上内存与垃圾回收/image-20240112222814202.png assets/上内存与垃圾回收/image-20240112223036655.png

-Xloggc:./logs/gc.log 先要建立對應目錄

日誌分析工具

assets/上内存与垃圾回收/image-20240112224239959.png

快捷

ctrl+shift+n 查類

其他

不加報錯,加後刪除不報錯

assets/上内存与垃圾回收/image-20231214101202060.png

assets/上内存与垃圾回收/image-20231214101152706.png

面試

assets/上内存与垃圾回收/image-20231215180335556.png

java

int num = 10;  
//s1 the declaration is thread safe  
//資料不會在每個堆疊框架之間共享  
public static void method1(){  
    //StringBuilder:執行緒不安全  
    StringBuilder s1 = new StringBuilder();  
    s1.append("a");  
    s1.append("b");  
  
}  
//sBuilder的操作過程:是執行緒不安全的  
//sBuilder可能被多個執行緒使用  
public static void method2(StringBuilder sBuilder){  
    sBuilder.append("a");  
    sBuilder.append("b");  
    //...  
}  
//s1的操作:是執行緒不安全的  
//有回傳,回傳可能被其他多個執行緒使用  
public static StringBuilder method3(){  
    StringBuilder s1 = new StringBuilder();  
    s1.append("a");  
    s1.append("b");  
    return s1;  
}  
//s1的操作:是執行緒安全的  
public static String method4(){  
    StringBuilder s1 = new StringBuilder();  
    s1.append("a");  
    s1.append("b");  
    return s1.toString();  
}

總結:當有內部資料的作用域不只在方法內時,就是方法不安全的 

assets/上内存与垃圾回收/image-20231225154716455.png

assets/上内存与垃圾回收/image-20231225162839430.png