本文基於 JDK1.8 闡述分析java
咱們都知道 Java 源文件經過編譯器編譯後,能產生相應的 .Class 文件,也就是字節碼文件。而字節碼文件經過 Java 虛擬機中的解釋器,編譯成特定機器上的機器碼。數據結構
Java 能跨平臺的緣由是由於:不一樣的平臺有不一樣的 JVM 版本,一個 Java 源文件被編譯成字節碼文件,被不一樣平臺的 JVM 翻譯成特定平臺下的機器碼從而運行。多線程
Java 虛擬機由三個子系統構成,分別是類加載子系統、JVM 運行時數據區和執行引擎,本文的重點是在 JVM 運行時數據區。jvm
類加載子系統將硬盤上的字節碼文件加載進內存,JVM 運行內存有一套本身的結構劃分如圖所示,最終程序要運行,須要操做系統分配相應的時間調度,由執行引擎去執行,才能獲得最終結果。函數
線程共享數據:容許被全部線程共享訪問的一塊內存區域。spa
線程私有數據:本線程私有的一塊內存區域操作系統
Java 虛擬機棧是線程私有的,它的生命週期與線程相同,線程啓動而產生,線程結束而消亡。線程
Java 虛擬機棧是描述 Java 方法執行的內存模型,用於存儲棧幀。翻譯
若是線程請求的棧深度大於虛擬機所容許的深度,將拋出 StackOverflowError 異常。3d
虛擬機棧能夠動態擴展,若是擴展時沒法申請到足夠的內存,就會拋出 OutOfMemoryError 異常。
除了 native 方法,幾乎全部的 Java 方法都是通虛擬機棧來實現方法的調用和執行(須要程序計數器、堆、方法區的配合)。
程序計數器是一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令。分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。
JVM 的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現,在同一時刻一個處理器內核只會執行一條線程,處理器切換線程時並不會記錄上一個線程執行到哪一個位置,因此爲了線程切換後依然能恢復到原位,每條線程都須要有各自獨立的程序計數器。
程序計數器存儲的是字節碼文件的行號,而這個範圍是可知曉的,在一開始分配內存時就能夠分配一個絕對不會溢出的內存。
當執行 Java 方法時,程序計數器存放 Java 字節碼的地址。實現上可能有兩種形式,一種是相對該方法字節碼開始處的偏移量,叫作 bytecode index(簡稱 bci)。另外一種是該 Java 字節碼指令在內存的地址,叫作 bytecode pointer(簡稱 bcp)。
Native 方法大多經過 C 實現,它的方法體不是由 Java 字節碼構成,沒法應用上述 Java 字節碼地址的概念,也就不須要存儲字節碼文件的行號。
Java 線程老是須要以某種形式映射到 OS 線程上,HotSpot VM 目前在大多數平臺上都使用 1:1 模型(原生線程模型),也就是每一個 Java 線程直接映射到一個 OS 線程上執行。此時 native 方法由原平生臺直接執行。
本地方法棧爲虛擬機使用到的 Native 方法服務。Native 方法是 Java 經過 JNI 直接調用本地 C/C++ 庫,能夠認爲是 Native 方法至關於 C/C++ 暴露給 Java 的一個接口,Java 經過調用這個接口從而調用 C/C++ 方法。與虛擬機棧同樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemoryError 異常。
不一樣於虛擬機棧的入/出棧,當線程調用 native 方法時,虛擬機只是簡單地動態鏈接並直接調用指定的 native 方法。
若是某個虛擬機實現的本地方法接口是使用 C 鏈接模型的話,那個他的本地方法棧就是 C 棧,當一個 C 函數調用另外一個 C 函數時,它的棧操做是肯定的。若是本地方法接口須要回調JVM 中的 Java 方法,該線程會保存本地方法棧的狀態並進入到另外一個Java棧。
虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並無強制規定,所以具體的虛擬機能夠自由實現它。經常使用的 HotSpot 虛擬機選擇合併了虛擬機棧和本地方法棧。
堆是 JVM 所管理的最大的一塊內存空間,主要用於存放各類類的實例對象。堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。
參數 | 說明 |
---|---|
-Xms | 堆內存初始大小 |
-Xmx | 堆內存最大容許大小 |
-Xss | 每一個線程的 Stack 大小 |
-XX:NewSize(-Xns) | 新生代初始大小 |
-XX:MaxNewSize(-Xmn) | 新生代最大容許大小 |
-XX:NewRatio | 設置新生代與老年代比值 |
-XX:SurvivorRatio | 設置 Survivor 與 Eden 比值 |
-XX:PermSize | 設置持久代初始內存大小(JDK8 之前) |
-XX:MaxPermSize | 設置持久代最大內存(JDK8 之前) |
-XX:MetaspaceSize | 設置元空間初始內存大小(JDK8 之後) |
-XX:MaxMetaspaceSize | 設置元空間最大內存(JDK8 之後) |
在堆中分配的內存,由 JVM 自動垃圾回收器來管理。關於 GC 詳情,以後再補充。
方法區是一種規範,不一樣的虛擬機的實現也不同。從 JDK 1.8 開始,元空間(Metaspace)取代了永久代(PermGen)成爲 HotSpot VM 對方法區的實現。方法區存儲加載進來的每個類的結構信息,能夠看作是將類(Class)的模板信息,保存在方法區裏
JDK8 之前,永久代是堆的一部分,和新生代、老年代的地址是連續的。JDK8 之後,元空間屬於本地內存,再也不屬於堆的一部分,它還有一個別名叫非堆(Non-Heap),因此元空間不存在 OOM 內存溢出的狀況。
當多個線程用到同一個類,而這個類還未被加載,則應該只有一個線程去加載類,其餘線程等待。