4 linux shell 指令碼
參考資料:尚矽谷JVM教程
3.1. 執行時資料區
3.1.1. 概述
本節主要講的是執行時資料區,也就是下圖這部分,它是在類載入完成後的階段
當我們通過前面的:類的載入-> 驗證 -> 準備 -> 解析 -> 初始化 這幾個階段完成後,就會用到執行引擎對我們的類進行使用,同時執行引擎將會使用到我們執行時資料區
記憶體是非常重要的系統資源,是硬碟和CPU的中間倉庫及橋樑,承載著作業系統和應用程式的實時執行JVM記憶體佈局規定了Java在執行過程中記憶體申請、分配、管理的策略,保證了JVM的高效穩定執行。不同的JVM對於記憶體的劃分方式和管理機制存在著部分差異。結合JVM虛擬機器規範,來探討一下經典的JVM記憶體佈局
我們把大廚後面的東西(切好的菜,刀,調料),比作是執行時資料區。而廚師可以類比於執行引擎,將通過準備的東西進行製作成精美的菜品
我們通過磁碟或者網路IO得到的資料,都需要先載入到記憶體中,然後CPU從記憶體中獲取資料進行讀取,也就是說記憶體充當了CPU和磁碟之間的橋樑
Java虛擬機器定義了若干種程式執行期間會使用到的執行時資料區,其中有一些會隨著虛擬機器啟動而建立,隨著虛擬機器退出而銷燬。另外一些則是與執行緒一一對應的,這些與執行緒對應的資料區域會隨著執行緒開始和結束而建立和銷燬。
灰色的為單獨執行緒私有的,紅色的為多個執行緒共享的。即:
-
每個執行緒:獨立包括程式計數器、棧、本地棧。
-
執行緒間共享:堆、堆外記憶體(永久代或元空間、程式碼快取)
每個JVM只有一個Runtime例項。即為執行時環境,相當於記憶體結構的中間的那個框框:執行時環境。
3.1.2. 執行緒
執行緒是一個程式裡的執行單元。JVM允許一個應用有多個執行緒並行的執行。 在Hotspot JVM裡,每個執行緒都與作業系統的本地執行緒直接對映。
當一個Java執行緒準備好執行以後,此時一個作業系統的本地執行緒也同時建立。Java執行緒執行終止後,本地執行緒也會回收。
作業系統負責所有執行緒的安排排程到任何一個可用的CPU上。一旦本地執行緒初始化成功,它就會呼叫Java執行緒中的run()方法。
3.1.3. JVM系統執行緒
如果你使用console或者是任何一個除錯工具,都能看到在後臺有許多執行緒在執行。這些後臺執行緒不包括呼叫public static void main(String[] args)
的main執行緒以及所有這個main執行緒自己建立的執行緒。
這些主要的後臺系統執行緒在Hotspot JVM裡主要是以下幾個:
-
虛擬機器執行緒:這種執行緒的操作是需要JVM達到安全點才會出現。這些操作必須在不同的執行緒中發生的原因是他們都需要JVM達到安全點,這樣堆才不會變化。這種執行緒的執行型別包括"stop-the-world"的垃圾收集,執行緒棧收集,執行緒掛起以及偏向鎖撤銷。
-
週期任務執行緒:這種執行緒是時間週期事件的體現(比如中斷),他們一般用於週期性操作的排程執行。
-
GC執行緒:這種執行緒對在JVM裡不同種類的垃圾收集行為提供了支援。
-
編譯執行緒:這種執行緒在執行時會將位元組碼編譯成到原生代碼。
-
訊號排程執行緒:這種執行緒接收訊號併發送給JVM,在它內部通過呼叫適當的方法進行處理。
3.2. 程式計數器(PC暫存器)
JVM中的程式計數暫存器(Program Counter Register)中,Register的命名源於CPU的暫存器,暫存器儲存指令相關的現場資訊。CPU只有把資料裝載到暫存器才能夠執行。這裡,並非是廣義上所指的物理暫存器,或許將其翻譯為PC計數器(或指令計數器)會更加貼切(也稱為程式鉤子),並且也不容易引起一些不必要的誤會。JVM中的PC暫存器是對物理PC暫存器的一種抽象模擬。
作用
PC暫存器用來儲存指向下一條指令的地址,也即將要執行的指令程式碼。由執行引擎讀取下一條指令。
它是一塊很小的記憶體空間,幾乎可以忽略不記。也是執行速度最快的儲存區域。
在JVM規範中,每個執行緒都有它自己的程式計數器,是執行緒私有的,生命週期與執行緒的生命週期保持一致。
任何時間一個執行緒都只有一個方法在執行,也就是所謂的當前方法。程式計數器會儲存當前執行緒正在執行的Java方法的JVM指令地址;或者,如果是在執行native方法,則是未指定值(undefined)。
它是程式控制流的指示器,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。
位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。
它是唯一一個在Java虛擬機器規範中沒有規定任何OutofMemoryError情況的區域。
舉例說明
public class PCRegisterTest {
public static void main(String[] args) {
//程式計數器的演示
int a = 10;
int b = 20;
int c = a + b;
}
}
使用javap反彙編指令對位元組碼檔案進行檢視:
public class PCRegisterTest {
public static void main(String[] args) {
//程式計數器的演示
int a = 10;
int b = 20;
int c = a + b;
//新增程式碼
String str = "hello";
System.out.println(a);
System.out.println(b);
}
}
再次使用工具檢視:
使用PC暫存器儲存位元組碼指令地址有什麼用呢?為什麼使用PC暫存器記錄當前執行緒的執行地址呢?
因為CPU需要不停的切換各個執行緒,這時候切換回來以後,就得知道接著從哪開始繼續執行。
JVM的位元組碼直譯器就需要通過改變PC暫存器的值來明確下一條應該執行什麼樣的位元組碼指令。
PC暫存器為什麼被設定為私有的?
我們都知道所謂的多執行緒在一個特定的時間段內只會執行其中某一個執行緒的方法,CPU會不停地做任務切換,這樣必然導致經常中斷或恢復,如何保證分毫無差呢?為了能夠準確地記錄各個執行緒正在執行的當前位元組碼指令地址,最好的辦法自然是為每一個執行緒都分配一個PC暫存器,這樣一來各個執行緒之間便可以進行獨立計算,從而不會出現相互干擾的情況。
由於CPU時間片輪限制,眾多執行緒在併發執行過程中,任何一個確定的時刻,一個處理器或者多核處理器中的一個核心,只會執行某個執行緒中的一條指令。
這樣必然導致經常中斷或恢復,如何保證分毫無差呢?每個執行緒在建立後,都會產生自己的程式計數器和棧幀,程式計數器在各個執行緒之間互不影響。
CPU時間片
CPU時間片即CPU分配給各個程式的時間,每個執行緒被分配一個時間段,稱作它的時間片。
在巨集觀上:俄們可以同時開啟多個應用程式,每個程式並行不悖,同時執行。
但在微觀上:由於只有一個CPU,一次只能處理程式要求的一部分,如何處理公平,一種方法就是引入時間片,每個程式輪流執行。