目錄
1.JVM結構體系
?編輯
2.跨平臺特性?
3.JVM整體結構及內存模型
1.棧內存
1、棧幀:
1.局部變量表
2.操作數棧
3.動態鏈接
4.方法出口
2、創建對象
2.程序計數器:
3.方法區
?4.堆
5.本地方法區
6.總結
1.JVM結構體系
JDK、JRE 和 JVM的包含關系:
1)JDK=JRE+ 開發工具集(例如Javac,java編譯工具等)
2) JRE=JVM+JavaSE標準類庫(java核心類庫)
3) 如果只想運行開發好的 .class文件 只需要JRE
2.跨平臺特性?
.java文件經過javac指令變成.class字節碼文件?,再通過java命令進入到java虛擬機里面運行,同樣的文件到不同環境的jvm運行都會產生不同的二進制機器碼,字節碼是統一的,但JVM生成的機器碼會因環境而異
一次編譯,到處運行
每個不同版本的JDK內部都有對應的不同操作系統的jvm環境,也就是不同版本的jvm去實現的
3.JVM整體結構及內存模型
總共是有3塊部分,運行流程也是如下:
1.類加載子系統
2.運行時數據區(內存模型)
3.字節碼執行引擎
1.棧內存
我們來看一個簡單的代碼
比如我們有一個main主方法,當我們運行的時候會有一個主線程來運行這個方法,此時java虛擬機會在線程棧內分配一塊獨立的空間,用來存放我們線程執行過程中用到的局部變量。不同的線程執行都有自己的內存空間去放局部變量,這就是棧內存。每一個方法的局部變量都有在棧內存里面的一塊棧幀內存區域來存放,每個棧幀區域都是獨立的不會嵌套,這個棧就是數據結構的那個先進后出的棧,因此代碼的從外到內執行變成了從上到下執行,符合!
上圖代碼的jvm處理字節碼文件的指令:
1、棧幀:
棧幀的內部也有很多區域,如下圖:
1.局部變量表
一開始存的就是具體方法的局部變量a之類的
??數據類型?? | ??存儲方式?? | ??示例?? |
---|---|---|
??基本類型?? | 直接存儲值(int, float, boolean等) | int a = 10; |
??對象引用?? | 存儲指向堆內存對象的指針(reference) | String s = "hello"; |
后續會被操作數棧賦值?
2.操作數棧
比如上述代碼的int a=2,常量2先通過JVM內的指令iconst_1壓入操作數棧里,局部變量表中分配的一塊內存空間給變量a,然后通過指令istore_1使常量2先出棧,再存入局部變量表使a=2賦值,注意,在JVM中字節碼指令的執行是原子性的,istore_1??原子操作??,先彈出棧頂值,再存入局部變量表,保證每條指令在執行時 ??不會被線程調度打斷。
雖然運算過程發生在??操作數棧??內,但JVM執行算術指令時,??必須先將操作數從棧頂彈出??,運算完成后再將結果壓回棧頂
3.動態鏈接
在運行時確定方法的實際調用地址?,比如動態鏈接確定實際調用的compute()方法,我們調用這個方法的時候得去知道這個方法內部有哪些指令,因為compute()已經放入常量池里面了,相當于目前只是個符號,當運行到這個符號的時候需要去解析,加載時會解析所有方法的符號引用,但??非靜態方法的綁定推遲到運行時??(因多態,將符號引用Math.compute:()
存入??運行時常量池??,但??不解析具體地址??,因可能有子類覆蓋),所以compute()只能在程序運行的時候加載,程序運行的時候把符號引用轉換成對應代碼的內存直接地址(或者說是直接引用)?
4.方法出口
compute()
方法執行完要出computr()方法的棧幀回到main()方法的棧幀里面
2、創建對象
?
當我們new了一個對象之后,這個math對象會存入堆里面,但是此時棧里面的main()方法的棧幀里面也有一個局部變量表里的math變量,這兩個math的關系是:
棧:本質是一個??引用??,存儲堆里math對象的地址,類似于指針
堆:包含對象頭(類型指針、GC標記等)、實例數據(字段)和方法表(vtable)等實際內容
因此我們可以得到一個結論:棧里面的很多局部變量都是指向堆里面的地址
====================================================================
2.程序計數器:
程序計數器也是在一個線程里面的,和棧內存一樣,是線程私有??的內存區域。
作用:
1.?記錄當前線程正在執行的字節碼指令的地址?
2.存儲下一條要執行的指令地址?
????????當JVM執行字節碼時,程序計數器(PC寄存器)會??指向當前線程正在執行的指令的地址??
3.控制程序執行流程?
????????順序執行??:??字節碼執行引擎??會動態修改程序計數器的值
4.線程切換后恢復執行?
????????當線程被操作系統掛起(如時間片用完),PC會保存當前執行位置。
????????線程恢復時,JVM根據程序計數器的值繼續執行??,確保程序邏輯正確。
====================================================================
3.方法區
方法區存的是常量池,所以也叫運行時常量池
方法區=常量+靜態變量+類信息
當我們new了一個靜態變量的user對象,這個對象會存入堆里面,此時user變量是存入方法區里面的,因為他是靜態變量,所以這里也是方法區里面的user變量指向堆里面的user對象
因此我們又可以得到一個結論:方法區里面的很多靜態變量都是指向堆里面的地址
拓展一下常量池類型:
-
類文件常量池?? (Class File Constant Pool)
- 存儲在.class文件中
- 包含編譯期確定的各種符號引用和字面量
-
??運行時常量池?? (Runtime Constant Pool)
- 每個類/接口獨有的
- 在類加載時從類文件常量池創建
-
??字符串常量池?? (String Constant Pool)
- 專門存儲字符串字面量
- Java 7開始從方法區移到堆內存
-
??基本類型包裝類常量池??
- 如IntegerCache、LongCache等
- 緩存特定范圍內的基本類型包裝對象
-
??符號引用常量池?? (Symbol Table)
- JVM內部使用的符號表
- 存儲類、方法、字段等的符號引用
-
??動態常量池?? (Dynamic Constant Pool)
- Java 11引入
- 支持動態語言特性
-
??本地方法常量池?? (Native Method Constant Pool)
- 為本地方法調用服務的常量池
-
??匿名類常量池?? (Anonymous Class Constant Pool)
- 為匿名類特化的常量池結構
====================================================================
?4.堆
結構圖:
區域分為:
堆=年輕代(Eden+s0+s1)+老年代? ? ? ? s0+s1:Survivor區?
我們new出來的對象大部分都放在Eden區
1、如果Eden放滿了怎么辦?那么字節碼執行引擎會開啟垃圾收集線程(垃圾回收GC),會把無用的對象回收
所有GC Roots??共同作為起點,比如靜態變量區和方法區的對象開始找引用對象,當找到某個對象不再被GC Roots直接或間接引用,就是說沒有任何引用鏈連接到GC Roots,此時這條線上的所有節點都會被標記為非垃圾對象,因此會把這些對象從Eden復制到Survivor區s0,剩下的就是垃圾對象會被刪除。
2、當Eren第2次滿了,這時候會再次觸發上述流程(放入Eden里對象),只不過滿了之后回收的區域變成了Eren+s0,非垃圾對象會從Eren+s0區到s1區,剩下的垃圾對象再次被刪除。
3、如果第3次滿了,再次觸發上述流程,只不過回收的區域變成了Eren+s1,非垃圾對象會從Eren+s1區到s0區,剩下的垃圾對象再次被刪除。
4、每挪一次,對象的分段年齡會+1,一般達到15次會進入老年區;當放入s0或者s1的時候放不下也會直接放入老年區
注意:??靜態變量區屬于GC Roots??它本身不會被回收,而是通過它判斷堆內對象是否存活。通常會進入到老年代的有:靜態變量、對象池、緩存對象、spring容器里的對象
====================================================================
5.本地方法區
比如start()方法里面會調用一個本地方法接口是用C++寫的,通過native
關鍵字聲明的方法:
本地方法會去找.dll文件
====================================================================
6.總結
當你讀完讀明白整篇文章的時候,你應該就理解如下圖片了: