java進階——JVM
1、JVM概述
作用
Java 虛擬機就是二進制字節碼的運行環境,負責裝載字節碼到其內部,解釋/編譯為對 應平臺上的機器碼指令行,每一條 java 指令,java 虛擬機中都有詳細定義,如怎么取操 作數,怎么處理操作數,處理結果放在哪兒。
特點:
一次編譯到處運行
自動內存管理
自動垃圾回收功能
現在的 JVM 不僅可以執行 java 字節碼文件,還可以執行其他語言編譯后的字節碼文件,是一 個跨語言平臺.
JVM的位置
JVM 是運行在操作系統之上的,它與硬件沒有直接的交互。
構成部分:
JVM 整體組成可分為以下四個部分:
1.類加載器(ClassLoader)
2.運行時數據區(Runtime Data Area)
3.執行引擎(Execution Engine)
4.本地庫接口(Native Interface)
簡圖:
詳細圖:
各個組成部分的用途
程序在執行之前先要把 java 代碼轉換成字節碼(class 文件),jvm 首先需要 把字節碼通過一定的方式 類加載器(ClassLoader) 把文件加載到內存中 的運行時數據區(Runtime Data Area) ,而字節碼文件是 jvm 的一套指 令集規范,并不能直接交個底層操作系統去執行,因此需要特定的命令解析 器執行引擎(ExecutionEngine) 將字節碼翻譯成底層系統指令再交由 CPU 去執行,而這個過程中需要調用其他語言的接口 本地庫接口(Native Interface) 來實現整個程序的功能,這就是這 4 個主要組成部分的職責與 功能。
而我們通常所說的 JVM 組成指的是運行時數據區(Runtime Data Area),因為通常需要程序員調試分析的區域就是“運行時數據區”,或者 更具體的來說就是“運行時數據區”里面的 Heap(堆)模塊。
Java 代碼的執行流程
Java 編譯器編譯過程中,任何一個節點執行失敗就會造成編譯失敗。雖然各個 平臺的 java 虛擬機內部實現細節不盡相同,但是它們執行的字節碼內容卻是一 樣的。 JVM 主要任務就是負責將字節碼裝載到其內部,解釋/編譯為對應平臺上的機器 指令執行。JVM 使用類加載器(Class Loader)裝載 class 文件。 類加載完成后,會進行字節碼校驗,字節碼校驗通過之后 JVM 解釋器會把字節 碼翻譯成機器碼交由操作系統執行。 但不是所有的代碼都是解釋執行,JVM 對此作了優化,比如 HotSpot 虛擬機, 它本身提供了 JIT(Just In Time)編譯器.
JVM 架構模型
Java 編譯器輸入的指令流基本上是一種基于棧的指令集架構,另一種指令集架構 是基于寄存器的指令集架構.
基于棧式架構的特點
設計和實現更簡單,適用于資源受限的系統. 使用零地址指令方式分配,其執行過程依賴于操作棧,指令集更小,編譯器容易實 現.不需要硬件支持,可移植性好,更好實現跨平臺.
基于寄存器式架構特點:
指令完全依賴于硬件,可移植性差.
性能優秀,執行更高效.
完成一項操作使用的指令更少.
使用 javap -v class 文件可以將 class 文件反編譯為指令集.
所以由于跨平臺的設計,Java 指令集都是根據棧來設計的,不同 CPU 架構不同,
所以不能設計為基于寄存器的.
優點是跨平臺,指令集小,編譯器容易實現.
缺點是性能下降,實現同樣功能需要更多的指令.
2、JVM結構-類加載
類加載子系統
作用:
類加載器子系統負責從文件系統或者網絡中加載 class 文件。
主負責加載類, 有執行引擎執行,存放在方法區(元空間)
扮演者的是一個快遞員的角色
-
class file 存在于硬盤上,可以理解為設計師畫在紙上的模板,而最終這個模板 在執行的時候是要加載 JVM 當中來,根據這個模板實例化出 n 個一模一樣的實 例.
-
class file 加載到 JVM 中,被稱為 DNA 元數據模板,放在方法區中.
-
在.class–>JVM–>最終稱為元數據模板,此過程就要有一個運輸工具(類加 載器 Class Loader),扮演一個快遞員的角色.
類加載過程
1.加載
根據類的地址,從硬盤上讀取類的信息,
將信息讀入到方法區,生成Class類的對象
2.鏈接
驗證: 驗證字節碼文件格式是否是當前虛擬機所支持的文件格式,語法格式
準備: 為靜態成員分配默認值(int 默認值0) 注意static final在編譯期間賦值
解析: 將字節碼中符號引用 替換 成 直接引用
例如: 編寫代碼 方法1 中調用 方法2 (符號引用)
類加載到內存后把符號的引用地址 換成 內存的地址引用
3.初始化
類什么時候初始化
1 )創建類的實例,也就是 new 一個對象
2)訪問某個類或接口的靜態變量,或者對該靜態變量賦值
3)調用類的靜態方法
4)反射(Class.forName(“”))
5)初始化一個類的子類(會首先初始化子類的父類)
類的初始化順序
先初始化靜態的,多個靜態的按照從上向下的順序執行,
如果類有父類,則先初始化父類的靜態,然后是子類.
如果是創建對象,先調用父類的構造方法,然后是子類自己的構造方法
類加載器
站在JVM的角度劃分: 啟動類加載器(不是java語言寫的),
其他類加載(都是java語言寫的)
站在開發者的角度:
啟動類加載器(引導類加加載器)
這個類加載器使用 C/C++語言實現,嵌套在 JVM 內部.它用來加載 java 核心類
庫.
負責加載擴展類加載器和應用類加載器
加載<JAVA_HOME>lib
擴展類加載類器
是由java語言實現的 繼承自ClassLoader
負責加載 E:ProgramFilesJavajdk1.8.0_261jrelibext
應用程序類加載器(系統類加載器)
Java 語言編寫的,由 sun.misc.Launcher$AppClassLoader 實現.
派生于 ClassLoader 類.
負責加載用戶類
用戶自定義類加載器(例如tomcat)
雙親委派機制
類的加載時按需加載,使用時才會加載.
類加載時,加載器都會將類交給父級類加載器加載.
如果所有的父級加載沒有找到類,
則一級一級的向下委派查找.
如果都找不到,那么就會拋出異常.
目的: 為了安全考慮 避免了用戶自己寫的類覆蓋了系統中的類.
類的主動使用和被動使用
主動使用會觸發類的初始化
new
使用靜態變量 靜態方法
反射加載類
執行main方法
子類被初始化 父類也會觸發初始化
被動使用不會觸發了類的初始化
僅僅使用類的靜態常量 而且是直接賦字面量的那種
將類作為數組的類型聲明使用時不會觸發初始化
3、JVM 運行時數據區
堆,方法區(元空間) 主要用來存放數據 是線程共享的.
程序計數器,本地方法棧,虛擬機棧 是運行程序的,是線程私有的.
程序計數器
jvm中的程序計數器不是cpu中的寄存器, 可以理解為計數器.
是一塊非常小的內存空間,運行速度是最快的,不會出現內存溢出情況.
作用:記錄當前線程中的方法執行的位置. 以便于cpu在切換執行時,記錄程序執行的為位置.
在運行時數據區中唯一一個不會出現內存溢出的區域.
本地方法棧
當我們在程序中調用本地方法時,會將本地方法加載到 本地方法棧中執行.
也是線程私有的, 如果空間不夠,也會出現棧溢出錯誤. hashCode();
虛擬機棧
背景: java為了移植性好(跨平臺) 所以將運行程序的設計架構為棧結構運行, 而不是依賴于cpu的寄存器架構.
棧是運行時的單位(加載方法運行),
而堆是存儲的單位(存儲對象的).
作用運行方法 一個方法就是一個棧幀. 棧幀中包含( 局部變量(基本類型,引用地址) 方法地址,返回地址)
棧中的操作 入棧,出棧
棧中異常 StackOverflowError:線程請求的棧深度大于虛擬機所允許的深度。 遞歸調用方法次數過多
棧中存儲方法運行時需要的數據
棧的運行原理 第一個方法被加載 入棧 在方法中調用了其他方法, 另一個方法入棧 方法運行結束后出棧.
棧幀的結構:
局部變量表: 方法參數,定義的局部變量, 基本值類型直接存值, 引用類型存地址.
操作數棧
動態鏈接
方法返回地址