jvm虚拟机_一文入门jvm虚拟机
點擊上方「10分鐘編程」關注我呦
讓我們每天「博學」一點點
一文帶你理解JVM
1、jdk、jre、jvm的區(qū)別與聯(lián)系
jdk的全稱是Java Development kit(java開發(fā)工具包),我們可以把程序設計語言、java虛擬機、java類庫這三部分統(tǒng)稱為jdk,jdk是用于支持java程序開發(fā)的最小環(huán)境。Developer可以很容易的使用里面的方法以減少代碼量,里面同時包含jre和一些開發(fā)的小工具(如編譯工具javac),同時包含了jre。
jre的全稱是Java Running Environment(java運行時環(huán)境 ),可以把java類庫API中的javaSE的API子集和java虛擬機這兩部分統(tǒng)稱為JRE,JRE是支持java程序運行的標準環(huán)境。
jvm的全稱java virtual machine(java 虛擬機),它只認識XXX.class文件,虛擬機可以識別這種文件的字節(jié)碼指令并調(diào)用操作系統(tǒng)上的API,正是這個原因,java才可以跨平臺使用。
2、代碼是如何執(zhí)行的
jvm是一個軟件,它幫我們屏蔽了底層的操作系統(tǒng)、硬件、CPU指令層的細節(jié)
它的口號是Write Once,Run Everywhere.我們來看一下代碼的執(zhí)行流程。
圖中的Test.java文件是按照java語法規(guī)則編寫的源文件,是一種高級語言,.java文件經(jīng)javac編譯后就生成字節(jié)碼文件,字節(jié)碼文件是用于給java虛擬機執(zhí)行用的,該文件的格式規(guī)范受到java虛擬機的定義。而jvm的目的就是將字節(jié)碼文件Test.class翻譯為操作系統(tǒng)及硬件的指令,便于在不同的操作系統(tǒng)上執(zhí)行。
NOTE:jvm虛擬機并不是僅僅只針對java語言,像一些其它編程語言如Groovy、Scala和Kotlin也可以在jvm虛擬機上運行上,這些語言僅僅需要實現(xiàn)一個編譯器,通過該編譯器把源代碼文件編譯成JVM能識別的字節(jié)碼文件即可。
3、JVM的內(nèi)存結(jié)構(gòu)
3.1類加載子系統(tǒng)
在java虛擬機中,負責查找并裝載類的部分稱為裝載子系統(tǒng),裝載子系統(tǒng)用于定位和加載譯碼后的class文件。在加載階段,虛擬機需要完成以下事情
- 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為元空間中運行時的數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法這個類的各種數(shù)據(jù)訪問入口
加載過程下圖所示
類的加載是通過查詢路徑的方式進行的,加載階段既可以使用虛擬機里內(nèi)置的引導類加載器來完成,也可以由用戶自定義類加載器來完成。其加載順序如下
1、Bootstrap ClassLoader:啟動類加載器,加載存放在\lib目錄,或者被Xbootclasspath選項指定的jar包,如rt.jar、tools.jar
2、Extension ClassLoader:擴展類加載器,加載\lib\ext*.jar或者-java.ext.dirs指定目錄下的jar包
3、AppClassLoader:應用程序類加載器,加載Classpath或java.class.path所指定的目錄下的類和jar包
4、Custom ClassLoader:通過java.lang.ClassLoader的子類自定義加載class
實際上,上面描述的僅僅是類加載過程中的加載過程,類加載的整個過程包括:加載、驗證、準備、解析和初始化
字節(jié)碼--->加載--->驗證--->準備--->解析--->初始化,其中驗證、準備和解析階段可以統(tǒng)稱為鏈接階段。
下面我們講解每個階段的作用
- 驗證:驗證是鏈接階段的第一步,這一階段的目的是確保Class文件的字節(jié)流包含的信息符合《java虛擬機規(guī)范》的全部約束要求,確保這些信息被當做代碼運行后不會危害虛擬機自身的安全
- 準備:正式為類中定義的變量(靜態(tài)變量)分配內(nèi)存并設置類變量初始值階段。
- 解析:java虛擬機將常量池(元數(shù)據(jù)區(qū)的一部分)內(nèi)的符號引用替換為直接引用過程
- 初始化:類的初始化是類加載過程的最后一步,它的作用是真正開始執(zhí)行類中編寫的java程序代碼
類加載會將類的信息加入到元數(shù)據(jù)空間。
如果一個類型從被加載到虛擬機內(nèi)存開始,到出卸載為止,它的整個生命周期將在類加載的基礎上增加使用和卸載階段
3.2jvm內(nèi)存部分(運行時數(shù)據(jù)區(qū))
jvm在運行時會把它所管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域,宏觀上可以劃分為兩部分
1、線程私有數(shù)據(jù)區(qū)(3個部分)
- 程序計數(shù)器
①程序計數(shù)器是一塊內(nèi)存較小的空間,它可以看做是當前線程執(zhí)行的字節(jié)碼的行號指示器
②它是程序控制流的指示器,分支、循環(huán)、跳轉(zhuǎn)、異常處理、多線程恢復等基礎功能都需要依賴這個計數(shù)器來完成
③線程私有,各條線程之間計數(shù)器互不影響,獨立存儲,
④隨著線程的結(jié)束而結(jié)束,不需要垃圾回收
⑤不會出現(xiàn)OutOfMemoryError
- 虛擬機棧
與程序計數(shù)器一樣,java虛擬機棧也是線程私有的,不會被GC回收,它的生命周期與線程相同,java虛擬機棧描述的是java方法執(zhí)行線程的內(nèi)存模型:每個方法被執(zhí)行的時候(一個方法對應著一個棧幀),java虛擬機棧都會同步創(chuàng)建一個棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。當棧的深度大于虛擬機所允許的深度,將拋出StackOverflowError異常,如果java虛擬機的容量可以動態(tài)擴展,當棧擴展時無法申請到足夠的內(nèi)存時將會拋出OutOfMemoryError異常。棧里面運行方法,存放方法的局部變量名,變量名所指向的值(常量值、對象值等)都存放在堆上。每一個方法被調(diào)用直至執(zhí)行完畢的過程,就對應著一個棧幀在虛擬機棧從入棧到出棧的過程。
①局部變量表:是一組變量的存儲空間,用于存放方法的參數(shù)和方法內(nèi)部定義的局部變量,局部變量分為兩種,分別是基本數(shù)據(jù)類型和引用類型(引用類型指向堆中對象的地址),常量值指向元空間。
②操作棧:操作棧也被稱為操作數(shù)棧,它是一個后入先出的棧。當一個方法剛開始執(zhí)行的時候,這個方法的操作數(shù)棧是空的,在方法的執(zhí)行過程中,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容,也就是出棧和入棧操作,一個完整的方法執(zhí)行期間往往包含多個這樣入棧和出棧的過程。操作數(shù)棧中元素的數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴格匹配。
③動態(tài)鏈接:每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)鏈接。一個方法要調(diào)用其它方法,需要將這些方法的符號引用轉(zhuǎn)化為其內(nèi)存地址的直接引用,而符號引用存在于方法區(qū)中的運行時常量池,所有需要在運行時動態(tài)的將這些符號引用轉(zhuǎn)化為直接引用。
④返回地址:方法不管是正常執(zhí)行結(jié)束還是異常退出,需要返回方法被調(diào)用的位置。
- 本地方法棧
本地方法棧與虛擬機棧所發(fā)揮的作用是非常相似的,其區(qū)別在于虛擬機棧為虛擬機執(zhí)行java方法(也就是字節(jié)碼)服務,而本地方法棧則是為虛擬機使用到的本地(Native)方法服務。與虛擬機棧一樣,本地方法棧也會在棧深度溢出或者棧擴展失敗時分別拋出StackOverflowError和OutOfMemoryError。線程私有,不會被GC回收。
有的虛擬機直接將虛擬機棧和本地方法棧合二為一,不在單獨考慮。
總結(jié):線程私有的三個部分都是隨著線程執(zhí)行結(jié)束而結(jié)束(JVM就銷毀了虛擬機棧里面的棧幀)。
2、線程公有數(shù)據(jù)區(qū)(2個部分)
- 元空間
在jdk1.8之前,元空間所在的區(qū)域被稱為方法區(qū),方法區(qū)在jdk1.7時合并到了堆。
jdk1.8時,方法區(qū)所在的區(qū)域被稱為元空間,但是1.8仍然保留著方法區(qū)的概念,只不過實現(xiàn)方式不同,元空間與堆不相連,但與堆共享物理內(nèi)存,邏輯上可以認為在堆中。
元空間的特點
①線程共享
②存儲類信息、常量、運行時常量池、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)
③在jdk1.7之前,在HotSpot虛擬機上將方法區(qū)成為永久代,在jdk1.8時,完全放棄了永久代,改用了元空間
④因為效率問題,無垃圾回收
⑤空間不夠時,OutOfMemoryError
⑥設置元空間的大小--XX:Metaspace=10M-XX:MetaspaceSize=10M
運行時常量池的特點
運行時常量池是元空間的一部分,Class文件除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池表,用于存放編譯期生成的各種字面量的符號引用,這部分內(nèi)容將在類加載后存放到元空間的運行時常量池中。
總結(jié)
jdk1.6及之前:有永久代,常量池在方法區(qū)
jdk1.7:從某個版本開始去除永久代,常量池1.7放入堆中
jdk1.8及之后:無永久代,常量池1.8在元空間。
- 堆
java堆(heap)是虛擬機所管理的內(nèi)存中最大的一塊,java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建,幾乎所有的對象實例和數(shù)組都在堆上分配。
java堆是垃圾收集器管理的內(nèi)存區(qū)域,從回收的角度看,由于現(xiàn)代垃圾收集器大部分都是基于分代收集理論設計的,所以java堆可以分為新生代和老年代,新生代又可以分為Eden空間、From Survivor空間和To Survivor空間,無論怎樣劃分,都是為了更好的進行垃圾回收。
java堆可以被實現(xiàn)成固定大小的,也可以是擴展的(通過-Xmx和-Xms設定)。如果在java堆中沒有內(nèi)存完成實例分配,并且堆無法在擴展時,java虛擬機將會拋出OutOfMemory異常。
在堆中加載實例對象的順序
當老年代空間滿時,將會拋出OutOfMemory異常。
java堆溢出
不斷創(chuàng)建對象又不釋放,當對象到達一定數(shù)量時,無堆空間時將會產(chǎn)生堆溢出
內(nèi)存泄漏:GC Roots到對象之間有可達路徑卻無法回收(存在對象引用,卻沒有釋放)
內(nèi)存溢出:內(nèi)存溢出是指應用系統(tǒng)中存在無法回收的內(nèi)存或使用內(nèi)存過多,最終使得程序運行要用到的內(nèi)存大于能提供的最大內(nèi)存。在Java虛擬機中,GC Roots到對象之間無可達路徑,可以被收集,但對象還存活著,此時可以根據(jù)物理機內(nèi)存適當?shù)恼{(diào)大虛擬機參數(shù)-Xms、-Xmx,分析代碼是否對象生命周期過長。對象是否持有狀態(tài)時間過長。
參考文獻
[1]周志明.深入理解java虛擬機
[2]https://www.bilibili.com/video/BV13J411n72m?from=search&seid=7875422419866373500
看了這篇文章,你是否「博學」了點個「在看」,是對我最大的鼓勵!
總結(jié)
以上是生活随笔為你收集整理的jvm虚拟机_一文入门jvm虚拟机的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3月汽车投诉排行榜:极氪001位列第一、
- 下一篇: 曝苹果企业零售团队正在裁员 官方称其为“