入門
什么是JVM
JVM:Java Virtual Machine,Java虛擬機。
JVM是JRE(Java Runtime Environment)的一部分,安裝了JRE就相當于安裝了JVM,就可以運行Java程序了。JVM的作用:加載并執行Java字節碼(.class)文件。
常見的JVM
Java虛擬機要實現Java虛擬機規范,它定義了Java虛擬機的結構、指令集、類文件格式、類加載器、字節碼執行引擎等方面的內容。
JVM結構
執行引擎Execution Engine
JVM執行引擎通常由兩個主要組成部分構成:解釋器和即時編譯器(Just-In-Time Compiler,JIT Compiler)。
解釋器:當Java字節碼被加載到內存中時,解釋器逐條解析并直接執行字節碼指令。
即時編譯器(JIT Compiler):即時編譯器將字節碼動態地編譯為本地機器碼,之后再執行。即時編譯器根據運行時的性能數據和優化技術,對經常執行的熱點代碼進行優化,從而提高程序的性能。即時編譯器可以將經過優化的代碼緩存起來,以便下次再次執行時直接使用。
本地方法接口Native Interface
本地接口的作用是融合不同的編程語言為 Java 所用。當Java代碼調用本地方法(被native所修飾的方法)時,JVM會將控制權轉移到本地方法實現所在的本地庫。本地庫是一個包含本地方法實現的動態鏈接庫(DLL - windows函數庫)或共享對象文件(SO - Linux函數庫)。它是使用其他編程語言編寫的,通常是為了與底層操作系統或硬件進行交互。本地庫可以通過JNI加載到JVM中,并提供給Java代碼調用。
例如Thread類中有一些標記為native的方法 操作底層操作系統線程
本地方法棧Native Method Stack
本地方法棧(Native Method Stack):本地方法棧存儲了從Java代碼中調用本地方法時所需的信息。是線程私有的。
本地方法棧是JVM專門為調用非Java語言方法而設計的,它與操作系統和硬件交互,通過JNI為Java程序提供更強大的功能。每個線程都有自己的本地方法棧,保證本地方法的調用是獨立且線程安全的。
PC寄存器(程序計數器PC Register)
PC寄存器(程序計數器,Program Counter Register)是JVM中線程私有的一塊小內存區域。用于記錄當前線程正在執行的字節碼指令的地址或行號。它類似于一個指針,指向線程正在執行的字節碼指令的下一條指令。
類加載器ClassLoader
類加載器(ClassLoader)是JVM的一個關鍵組件,用于動態加載、鏈接和初始化Java類。它的主要職責是在程序運行時將類的字節碼加載到JVM中,以便JVM可以執行這些類。類加載器是一個負責加載類的對象,用于實現類加載過程中的加載這一步。每個 Java 類都有一個引用指向加載它的 ClassLoader。數組類不是通過 ClassLoader 創建的(數組類沒有對應的二進制字節流),是由 JVM 直接生成的。
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,即雙親委派模型
虛擬機棧stack
棧也叫棧內存,主管Java程序的運行,是在線程創建時創建,每個線程都有自己的棧,它的生命周期是跟隨線程的生命周期,線程結束棧內存也就釋放,是線程私有的。線程上正在執行的每個方法都各自對應一個棧幀(Stack Frame)。
JVM對Java棧的操作只有兩個,就是對棧幀的壓棧和出棧,遵循“先進后出”或者“后進先出”原則。
一個線程中只能由一個正在執行的方法(當前方法),因此對應只會有一個活動的當前棧幀。
棧溢出(StackOverflowError):通常在遞歸調用
棧幀
棧幀是一個內存區塊,是一個數據集,包含方法執行過程中的各種數據信息。
局部變量表(Local Variables)
也叫本地變量表。存儲方法參數和方法體內的局部變量:8種基本類型變量、對象引用(reference)。
操作數棧(Operand Stack)
作用:也是一個棧,在方法執行過程中根據字節碼指令記錄當前操作的數據,將它們入棧或出棧。用于保存計算過程的中間結果,同時作為計算過程中變量的臨時存儲空間。
public class OperandStackDemo {public static void main(String[] args) {int i = 15;int j = 8;int k = i + j;}
}
動態鏈接(Dynamic Linking)
作用:可以知道當前幀執行的是哪個方法。指向運行時常量池中方法的符號引用。程序真正執行時,類加載到內存中后,符號引用會換成直接引用。
方法返回地址(Return Address)
可以知道調用完當前方法后,上一層方法接著做什么,即“return”到什么位置去。存儲當前方法調用完畢后下一條指令的地址
完整一個線程內存結構
java -Xss 可以設置棧大小
方法區Method Area
被所有線程共享,它用于存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯后的代碼緩存等
堆heap
堆是線程共享的
堆、棧、方法區的關系
HotSpot是使用指針的方式來訪問對象:
- 堆內存用于存放對象和數組
- 堆中會存放指向對象類型數據的地址
- 棧中會存放指向堆中的對象的地址
堆空間
一個Java程序運行起來對應一個進程,一個進程對應一個JVM實例,一個JVM實例中有一個運行時數據區。
在 Java 虛擬機(JVM)中,堆空間是管理 Java 對象的內存區域。堆空間的主要作用是動態分配和管理 Java 對象的內存,在JVM啟動的時候被創建,并且一個JVM實例只存在一個堆內存,堆內存的大小是可以調節的。
堆空間劃分
堆內存邏輯上分為三部分:
- Young Generation Space 新生代/年輕代 Young/New
- Tenured generation space 老年代/養老代 Old/Tenured
- Permanent Space/Meta Space 永久代/元空間 Permanent/Meta
新生代又劃分為:
- 伊甸園區(Eden space)
- 和幸存者區(Survivor space) 。
幸存者區有兩個: - S0區(Survivor 0 space)
- S1區(Survivor 1 space)
堆內存內部空間所占比例:
- 新生代與老年代的默認比例: 1:2
- 伊甸園區與幸存者區的默認比例是:8:1:1
配置堆大小
-Xms600m -Xmx600m -Xmn200m
-
Xms表示堆的起始內存,等價于-XX:InitialHeapSize,默認是物理電腦內存的1/64。
-
Xmx表示堆的最大內存,等價于-XX:MaxHeapSize,默認是物理電腦內存的1/4。
通常會將-Xms和-Xmx配置相同的值,目的是為了在Java垃圾回收機制清理完堆區后,不需要重新分隔計算堆區的大小,從而提高性能。
JDK1.8及之后堆空間
堆空間工作流程
學不明白的了解即可
垃圾回收GC
在C/C++這類沒有自動垃圾回收機制的語言中,一個對象如果不再使用,需要手動釋放,Java中為了簡化對象的釋放,引入了自動的垃圾回收(Garbage Collection簡稱GC)機制。通過垃圾回收器來對不再使用的對象完成自動的回收,垃圾回收器主要負責對堆上的內存進行回收。其他很多現代語言比如C#、Python、Go都擁有自己的垃圾回收器
線程不共享的部分,都是伴隨著線程的創建而創建,線程的銷毀而銷毀。因此線程不共享的程序計數器、虛擬機棧、本地方法棧中沒有垃圾回收。
方法區的垃圾回收
方法區中能回收的內容主要就是不再使用的類。判定一個類可以被卸載。需要同時滿足下面三個條件:
1、此類所有實例對象沒有在任何地方被引用,在堆中不存在任何該類的實例對象以及子類對象。
2、該類對應的 java.lang.Class 對象沒有在任何地方被引用。
3、加載該類的類加載器沒有在任何地方被引用。
堆的垃圾回收
Java堆中的對象是否能被回收,是根據對象是否被引用來決定的。如果對象被引用了,說明該對象還在使用,不允許被回收。
垃圾判斷
判斷堆上的對象是否被引用方法:引用計數法(已摒棄)和可達性分析法。
可達分析法
將一系列GC Root的集合作為起始點,按照從上至下的方式搜索所有能夠被該合集引用到的對象(是否可達),并將其加入到該和集中,這個過程稱之為標記(mark),被標記的對象是存活對象。 最終,未被探索到的對象便是死亡的,可以回收的。
垃圾回收算法
當成功區分出內存中存活對象和死亡對象后,GC接下來的任務就是執行垃圾回收。有以下算法:
標記-清除算法(Mark-Sweep)
復制算法(Copying)
標記壓縮算法(Mark-Compact)
分代收集算法(Generational-Collection)
垃圾收集器
如果說收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現
七款經典垃圾收集器:
算法和收集器了解即可