Java虛擬機這一塊 —— 深入理解java虛擬機(jvm)

爲什麼要了解虛擬機

JVM 不單單隻支持 Java 語言,也支持其他語言(Scala、Kotlin、Groovy 等等) 區塊鏈 2.0–以太坊(比特幣是區塊鏈 1.0) 中提供了 EVM 的虛擬機,它的實現和 JVM 類似,基於棧、生成腳本編譯成字節碼來執行。知識通用。(理論大於實際)

虛擬機歷史

瞭解即可,無需關注

  • 解釋執行和編譯執行(針對字節碼的執行) 解釋執行就是邊翻譯爲機器碼邊執行、即時編譯(編譯執行)就是先將一個方法中的所有字節碼全部編譯成機器碼之後再執行。
  • Hotspot 採用的是先解釋執行,到了一定時機後熱點代碼(多次執行、循環等)再翻譯成機器碼 熱點代碼探測技術(通過執行計數器找到最有編譯價值的代碼,如果代碼用得非常頻繁,就會把這些代碼編譯成本地代碼)。
  • JRockit 採取的方法是在執行 class 時直接編譯爲機器碼(Java 程序啓動速度會比較慢)
  • J9 和 Hotspot 比較接近,主要是用在 IBM 產品(IBMWebSphere 和 IBM 的 AIX 平臺上),華爲有的項目用的 J9。
  • 谷歌:GoogleAndroidDalivkVM:使用的寄存器架構,執行 dex(DalvikExecutable)通過 class 轉化而來。

未來的 Java 技術

模塊化

OSGI(動態化、模塊化),應用層面就是微服務,互聯網的發展方向 混合語言:多個語言都可以運行在 JVM 中,google 的 Kotlin 成爲了 Android 的官方語言。Scala(Kafka)

多核並行

CPU 從高頻次轉變爲多核心,多核時代。JDK1.7 引入了 Fork/Join,JDK1.8 提出 lambda 表達式(函數式編程天生適合並行運 行)

豐富語法

JDK5 提出自動裝箱、泛型(併發編程講到)、動態註解等語法。JDK7 二進制原生支持。try-catch-finally 至 try-with-resource

64 位

雖然同樣的程序 64 位內存消耗比 32 位要多一點,但是支持內存大,所以虛擬機都會完全過渡到 64 位,32 位的 JVM 有 4G 的 堆大小限制。

更強的垃圾回收器(現在主流 CMS、G1)

JDK11 –ZGC(暫停時間不超過 10 毫秒,且不會隨着堆的增加而增加,TB 級別的堆回收)): 有色指針、加載屏障。JDK12 支持併發類卸載,進一步縮短暫停時間 JDK13(計劃於 2019 年 9 月)將最大堆大小從 4TB 增加到 16TB

JavaSE 體系架構

JavaSE,Java 平臺標準版,爲 JavaEE 和 JavaME 提供了基礎。

JDK

Java 開發工具包,JDK 是 JRE 的超集,包含 JRE 中的所有內容,以及開發程序所需的編譯器和調試程序等工具。

JRE

JavaSE 運行時環境 ,提供庫、Java 虛擬機和其他組件來運行用 Java 編程語言編寫的程序。主要類庫,包括:程序部署發佈、用 戶界面工具類、繼承庫、其他基礎庫,語言和工具基礎庫

JVM

java 虛擬機,負責 JavaSE 平臺的硬件和操作系統無關性、編譯執行代碼(字節碼)和平臺安全性
在這裏插入圖片描述

運行時數據區域

  • 這個是抽象概念,內部實現依賴寄存器、高速緩存、主內存(具體要分析 JVM 源碼 C++語言實現,沒必要看)
  • 計算機的運行=指令+數據,指令用於執行方法的,數據用於存放數據和對象的。
  • 虛擬機棧----執行 java 方法、本地方法棧—執行本地方法、程序計數器—程序執行的計數器
  • Java 中的數據:變量、常量、對象、數組相關。
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述

線程私有區域

程序計數器

較小的內存空間,當前線程執行的字節碼的行號指示器;
各線程之間獨立存儲,互不影響(面試可能問到爲什麼需要) 如果線程正在執行的是一個 Java 方法,則指明當前線程執行的代字節碼行數 如果正在執行的是 Natvie 方法,這個計數器值則爲空(Undefined) 此內存區域是唯一一個不會出現 OutOfMemoryError 情況的區域。

注:jvm中唯一不會oom

什麼是棧?

棧(Stack):數據結構
  • 入口和出口只有一個
  • 入棧
  • 出棧
特點:

先進後出(FILO)

出棧入棧的圖示:
在這裏插入圖片描述

虛擬機棧

存儲當前線程運行方法所需的數據,指令、返回地址

  • 每個線程私有的,線程在運行時,在執行每個方法的時候都會打包成一個棧幀,存儲了局部變量表,操作數棧,動態鏈接,方法出口等信息,然後放入 棧。每個時刻正在執行的當前方法就是虛擬機棧頂的棧楨。方法的執行就對應着棧幀在虛擬機棧中入棧和出棧的過程。
  • 棧的大小缺省爲 1M
  • 可用參數 –Xss 調整大小,例如-Xss256k
  • 在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數棧都已經完全確定了,並且寫入到方法表的 Code 屬性之中,因此一個棧幀需要分 配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。
棧幀

每個方法在執行的同時都會創建一個棧幀

棧幀還可以劃分爲:

  • 局部變量表
    顧名思義就是局部變量的表,用於存放我們的局部變量的。首先它是一個 32 位的長度,主要存放我們的 Java 的八大基礎數據類型,一般 32 位就可以存放下,如果是 64 位的就使用高低位佔用兩個也可以存放下,如果是局部的一些對象,比如我們的 Object 對象,我們只需要存放它的一個引用 地址即可。(基本數據類型、對象引用、returnAddress 類型)
  • 操作數棧
    存放我們方法執行的操作數的,它就是一個棧,先進後出的棧結構,操作數棧,就是用來操作的,操作的的元素可以是任意的 java 數據類 型,所以我們知道一個方法剛剛開始的時候,這個方法的操作數棧就是空的,操作數棧運行方法是會一直運行入棧/出棧的操作
  • 動態鏈接
    Java 語言特性多態(需要類加載、運行時才能確定具體的方法)
  • 返回地址(方法出口)
    正常返回(調用程序計數器中的地址作爲返回)

三步曲: 恢復上層方法的局部變量表和操作數棧、 把返回值(如果有的話)壓入調用者棧幀的操作數棧中、 調整 PC計數器的值以指向方法調用指令後面的一條指令、 異常的話(通過異常處理器表<非棧幀中的>來確定)

本地方法棧

本地方法棧保存的是native方法的信息,當一個JVM創建的線程調用native方法後,JVM不再爲其在虛擬機棧中創建棧幀,JVM只是簡單的動態鏈接並直接調用native方法

虛擬機規範無強制規定,各版本虛擬機自由實現,HotSpot直接把虛擬機棧與本地方法棧合二爲一

線程共享區域(針對數據的存放)

方法區(永生代(JDK1.7和以前)、元空間(JDK1.8))

用於存儲已經被虛擬機加載的類信息,常量(“zdy”,"123"等),靜態變量(static 變量)等數據,可用以下參數調整:
jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8 以後:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8 以後大小就只受本機總內存的限制
如:-XX:MaxMetaspaceSize=3M

  • 類信息
    類的完整有效名、返回值類型、修飾符(public,private…)、變量名、方法名、方法代碼、這個類型直接父類的完整有效名(除非這個 類型是 interface 或是 java.lang.Object,兩種情況下都沒有父類)、類的直接接口的一個有序列表

  • 常量

  • 靜態變量

  • 即時編譯期編譯後的代碼

注:方法區中的信息一般不會被回收

java堆

幾乎所有對象都分配在這裏,也是垃圾回收發生的主要區域
可用以下參數調整:

  • -Xms:堆的最小值;
  • -Xmx:堆的最大值;
  • -Xmn:新生代的大小;
  • -XX:NewSize;新生代最小值;
  • -XX:MaxNewSize:新生代最大值;

例如-Xmx256m

運行時常量池
符號引用(一個概念)
  • 一個 java 類(假設爲 People 類)被編譯成一個 class 文件時,如果 People 類引用了 Tool 類,但是在編譯時 People 類並不知道引用類的實際內存地址,因 此只能使用符號引用來代替。
  • 而在類裝載器裝載 People 類時,此時可以通過虛擬機獲取 Tool 類的實際內存地址,因此便可以既將符號 org.simple.Tool 替換爲 Tool 類的實際內存地址, 及直接引用地址。
  • 即在編譯時用符號引用來代替引用類,在加載時再通過虛擬機獲取該引用類的實際地址.
  • 以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存佈局是無關的, 引用的目標不一定已經加載到內存中。
字面量
  • 文本字符串 Stringa=“abc”,這個 abc 就是字面量,
  • 八種基本類型 int a=1; 這個 1 就是字面量
  • 聲明爲 final 的常量

JDK1.6時運行時常量池存在於方法區中,JDK1.7時運行時常量池放在了堆中

JDK1.7=>JDK1.8
在這裏插入圖片描述

直接內存(JVM直接管理不了的)

不是虛擬機運行時數據區的一部分,也不是java虛擬機規範中定義的內存區域;

  • 如果使用了NIO,這塊區域會被頻繁使用,在java堆內可以用directByteBuffer對象直接引用並操作;
  • 這塊內存不受java堆大小限制,但受本機總內存的限制,可以通過MaxDirectMemorySize來設置(默認與堆內存最大值一樣),所以也會出現OOM異常;
  • 避免了在Java 堆和Native 堆中來回複製數據,能夠提高效率

在這裏插入圖片描述

站在線程角度來看

虛擬機棧、本地方法棧、程序計數器三個區域的生命週期和線程相同。
線程共享區域:就複雜多了,後續完善
在這裏插入圖片描述

深入辨析堆和棧

功能

  • 以棧幀的方式存儲方法調用的過程,並存儲方法調用過程中基本數據類型的變量(int、 short、 long、 byte、 float、
    double、boolean、char 等)以及對象的引用變量,其內存分配在棧上,變量出了作用域就會自動釋放;
  • 而堆內存用來存儲 Java 中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆內
    存中;

線程獨享還是共享

  • 棧內存歸屬於單個線程,每個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見,即棧內存可
    以理解成線程的私有內存。
  • 堆內存中的對象對所有線程可見。堆內存中的對象可以被所有線程訪問。

空間大小

棧的內存要遠遠小於堆內存

棧溢出

參數:-Xss256k

  • java.lang.StackOverflowError 一般的方法調用是很難出現的,如果出現了要考慮是否有無限遞歸。
  • 虛擬機棧帶給我們的啓示:方法的執行因爲要打包成棧楨,所以天生要比實現同樣功能的循環慢,所以樹的遍歷算法中:遞歸和非遞歸(循環來實現)都有存在的意義。遞歸代碼簡潔,非遞歸代碼複雜但是速度較快。
  • OutOfMemoryError:不斷建立線程。(一般演示不出,演示出來機器也死了)

在貼個圖
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章