JVM - 進入Java虛擬機的真實世界

JVM - 進入Java虛擬機的真實世界

相信對Java編程有了一定程度瞭解的同學,多多少少都已經聽說過、瞭解過Java虛擬機。就算你還未開始學習Java編程但已經打算計劃去學習,那你也肯定聽說過一本書《深入理解Java虛擬機 JVM高級特性與最佳實踐 》。在我當時正計劃踏入Java這個大家庭的時候,我也提前買了一堆書籍,其中就有它。直到兩年後,我對自己技術的不滿足並且制定了一系列計劃之後,我才重新翻開這本書。過去的我對JVM的瞭解僅僅只是冰山一角,從這裏開始我們一起慢慢去進入Java虛擬機真實的世界。
相信大家學習Java那就一定對Java語言爲何保持着優勢並且經久不衰十分清楚,其中的一個配件功不可沒-JVM,這是塑造了Java代碼一處編譯、處處運行的根本。如果對其他編程語言,例如C或者C++有了解的同學應該能夠很清楚,在C和C++當中對於內存的管理,開發者擁有至高無上的權利,但同時開發者也需要對其中每一對象從創建到銷燬都進行維護。
而對於Java開發者來說,虛擬機的自動內存管理機制能夠在大部分情況下都幫助我們管理好我們所使用的內存,對於已經十分成熟的虛擬機技術很少會出現內存泄漏以及內存溢出的問題,這對於我們Java開發者來說簡直就是一個十分愉悅的事情。但與此同時,這樣完善的內存管理機制也是一把利刃。一旦出現內存泄漏或者溢出等問題,若我們對虛擬機真正的管理機制一知半解甚至不清楚,那問題將會十分難以解決甚至是致命的。

1.探索虛擬機的內存區域

 當我們運行一個Java程序的時候,JVM會將Java程序在運行過程中所用到的內存進行管理起來,將其分成若干個不同的數據區域。每個區域都有其獨特定義、用途以及特性。這裏我們先來看一下程序運行時數據區是如何分配的。

1.1 運行時數據區

 對於運行時數據區的分配,從JDK8開始還進行了一些改變。

JDK1.8之前
在這裏插入圖片描述
JDK1.8
在這裏插入圖片描述

 這裏我們可以很清晰地看到JDK1.8起徹底將方法區移除並替換成了直接內存中新增加的元空間,另外把運行時常量池放在了堆內分配。
 另外每個數據區分佈也不同,主要區別是線程共享以及線程私有。這裏我們大概看一下,下面會對每個數據區單獨介紹。

  1. 線程共享:堆、方法區
  2. 線程隔離/私有:虛擬機棧、本地方法棧、程序計數器

1.2 程序計數器

1.2.1 程序計數器是什麼?

程序計數器實際上是一塊較小的內存空間,我們可以把它看作是當前線程所執行的字節碼的行號指示器。字節碼解釋器運行時主要就是通過修改當前線程的程序計數器的值,來依次讀取需要執行的字節碼指令,我們程序中的分支、循環、跳轉、異常處理、線程恢復等基礎功能都是依賴程序計數器來完成的。

1.2.2 爲什麼使用程序計數器?

 我們都知道線程是一個獨立的執行單元,由CPU所控制執行的。對操作系統有了解的同學應該聽說過時間片,其實在我們應用程序運行過程中每個線程都被會分配一個時間段,也就是CPU分配給各個程序運行的時間。當我們同時運行多個應用程序時,其實本質上是每個線程不停輪流切換去使用CPU的資源,看似是多個應用程序在同時執行,其實本質上若只有一個CPU(內核),則一次只能處理一個時間片中的指令。
 所以爲了線程來回切換時能夠恢復到上一次正確的執行位置,每個線程都會自己維護一個獨立的程序計數器,獨立存儲且不受其他線程的影響,是一塊線程私有的內存空間。

 這裏我們根據實際情況來做一個講解。這裏我們先定義一個簡單的User類。

public class User{
	
	private Integer age;

	private String name;

	public Integer getAge(){
		return age;
	}

	public String getName(){
		return name;
	}
}

 我們對其進行編譯之後,通過javap -l查看編譯後的字節碼文件。
在這裏插入圖片描述
 這裏可以看到每一個方法上開始都會有對應的行號,這就是我們程序計數器所需要記錄的數據。
在這裏插入圖片描述
 例如我們在時間片1時,CPU將資源分配給了線程1,此時線程1執行到了getAge()方法的位置,CPU將時間片分配給了線程2,此時線程1的程序計數器就會記錄當前getAge()所在行。並將時間片切換至線程2。
在這裏插入圖片描述
 線程2在時間片2內又執行到了getName()的位置,此時線程2的程序計數器也會將getName()的位置記錄下來,並切換到時間片3。假設時間片分配是均勻的,則時間片3時線程1恢復執行,這時就需要使用到之前線程1的程序計數器所記錄的位置用以恢復到上一次線程1所執行的字節碼所在行。多個線程之間就是這樣來回切換運行的。
 這裏因爲線程正在執行的是一個Java方法,所以這個計數器記錄的是正在執行的虛擬機字節碼指
令的地址;如果正在執行的是Native方法,這個計數器值則爲空(Undefined)。
 由於程序計數器是JVM默認分配的不由開發者控制,它的生命週期隨着線程的創建而創建,隨着線程的結束而死亡。所以程序計數器也是唯一一個不會出現OutOfMemoryError的內存區域,

ps:這裏還是推薦大家有時間讀一讀《深入理解Java虛擬機 JVM高級特性與最佳實踐 》這本書,雖然與最新的技術有一些差異,但是其中除了對JVM有詳細的講解,還對Java語言以及虛擬機的發展歷史有不錯的介紹。

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