1. 程式人生 > >JVM——記憶體模型(一):程式計數器

JVM——記憶體模型(一):程式計數器

擁有最高權利卻又從事著平民百姓的基礎工作是一種什麼樣的體驗?

對於從事C、C++的程式設計師來說,這種感覺他們實在是熟悉得不能再熟悉了。在記憶體管理的領域,不論是物件的生命的開始,還是終結,所有物件的命運都被他們掌握在手裡。他們既是掌管最高權利的皇帝,也是從事基礎工作的平民。

那麼Java程式設計師又是什麼樣的?

對於Java程式設計師來說,他們的體驗在這一方面也許就沒有C、C++他們的那麼豐富了。為什麼?因為咱們有虛擬機器呀!

在虛擬機器自動記憶體管理機制的幫助下,Java程式設計師不再需要為每一個new操作去寫配對的delete/free程式碼,因此更加不容易出現記憶體洩漏和記憶體溢位的問題(不明白什麼叫記憶體洩漏或者記憶體溢位的夥伴們可以參考:

JVM——記憶體溢位和記憶體洩漏的區別)。

不過,將記憶體控制的權利完全交給虛擬機器,這樣真的好嗎?

上文說到有了虛擬機器之後更不容易出現記憶體洩漏和記憶體溢位,但是這並不代表這些事情不會發生。若是發生了記憶體洩漏和記憶體溢位,那該怎麼辦呢?

還能怎麼辦?當然是屁顛屁顛地跑去學習虛擬機器是如何使用記憶體的唄!

當然啦,學習虛擬機器也許很枯燥,不過這段時間本帥博主將與大家一起了解認識一下JVM的相關知識,想必大家就不會孤單啦~。今天我們就來了解一下執行時資料區域中的程式計數器。

1.執行時資料區域

上文中我們說到Java將記憶體管理的權力交給了虛擬機器,那麼虛擬機器所管理的記憶體都有什麼呢?我們來看看下面這張圖:

 從上面這個圖我們可以看出來,執行時資料區域包括:方法區、堆、虛擬機器棧、本地方法棧與資料庫,其中方法區和堆是所有執行緒共享的資料區域。

那麼五個東西到底是個啥呢?我們這段時間就一起來認識一下。接下來今天先認識一下程式計數器。

 2.程式計數器

 做學問就要有敢於問,一看到程式計數器,“這東西是個嘛?”就要立馬脫口而出。

這東西是個嘛?

今天我們請來了《深入理解Java虛擬機器》的作者周志明大佬,讓我們來聽聽這位大佬是如何定義程式計數器的。

周大佬:程式計數器,英文名叫Program Counter Register,它是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。

那這個程式計數器又能做點什麼呢?

周大佬:在我們虛擬機器的概念(注意,這裡說的是僅僅是概念模型噢,畢竟各種虛擬機器可能會通過一些更加高效的方式去事先,)裡面,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,像什麼分支、迴圈、跳轉、異常處理、執行緒恢復等功能都需要依賴這個程式計數器來完成。

注:如果不瞭解位元組碼的話呢,可以參考:雜談——探祕位元組流與字元流

看了周志明大佬的描述,也許有的夥伴可能會產生程式計數器是否是多餘的疑問。

因為沿著指令的順序執行下去,即使是分支跳轉這樣的流程,跳轉到指定的指令處按順序繼續執行是完全能夠保證程式的執行順序的。假設程式永遠只有一個執行緒,這個疑問沒有任何問題,也就是說並不需要程式計數器。

但實際上程式是通過多個執行緒協同合作執行的。所以說,程式計數器還是有用武之地的。既然是多執行緒,那程式計數器該怎麼分配呢?

首先我們要搞清楚JVM的多執行緒實現方式。

JVM的多執行緒是通過CPU時間片輪轉(即執行緒輪流切換並分配處理器執行時間)演算法來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說則是一個核心)都只會執行一條執行緒中的指令

也就是說,某個執行緒在執行過程中可能會因為時間片耗盡而被掛起,而另一個執行緒獲取到時間片開始執行。當被掛起的執行緒重新獲取到時間片的時候,它要想從被掛起的地方繼續執行,就必須知道它上次執行到哪個位置。那麼如何知道自己執行到哪一個位置呢,由誰來記錄?

在JVM中,通過程式計數器來記錄某個執行緒的位元組碼執行位置。因此,為了執行緒切換之後能夠恢復到一個正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,即具備執行緒隔離的特性,各條執行緒之間計數器互不影響,獨立儲存(我們稱這一類記憶體區域為“執行緒私有”的記憶體)。

需要注意,程式計數器記錄的值分兩種情況

  • 如果執行緒正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令的地址
  • 如果正在執行的是Native方法,這個計數器的值為空(Undefined)。

 

3.程式計數器的特點

程式計數器的特點主要有以下六點:

  1. 執行緒隔離性(即程式計數器的記憶體空間是執行緒私有的),每個執行緒工作時都有屬於自己的獨立計數器(上文提到過)。
  2. 執行java方法時,程式計數器是有值的,且記錄的是正在執行的位元組碼指令的地址(參考上一小節的描述)。
  3. 執行native本地方法時,程式計數器的值為空(Undefined)。因為native方法是java通過JNI直接呼叫本地C/C++庫,可以近似的認為native方法相當於C/C++暴露給java的一個介面,java通過呼叫這個介面從而呼叫到C/C++方法。由於該方法是通過C/C++而不是java進行實現。那麼自然無法產生相應的位元組碼,並且C/C++執行時的記憶體分配是由自己語言決定的,而不是由JVM決定的。具體流程且看下圖:
  4. 程式計數器佔用記憶體很小,在進行JVM記憶體計算時,可以忽略不計。
  5. 程式計數器,是唯一一個在java虛擬機器規範中沒有規定任何OutOfMemoryError的區域。上文說到程式計數器,儲存的是當前執行的位元組碼的偏移地址。當執行到下一條指令的時候,改變的只是程式計數器中儲存的地址,並不需要申請新的記憶體來儲存新的指令地址;因此,永遠都不可能記憶體溢位的。
  6. 執行緒計數器,必須是執行緒被建立開始執行的時候,就要一同被建立。一個執行緒在執行的任何期間,都會失去CPU執行權,因此,我們要從一個執行緒被建立開始執行,就要無時無刻的記錄著該執行緒當前執行到哪裡了。

 

 

 

好啦,以上就是關於程式計數器的相關總結,如果大家有什麼更具體的發現或者發現文中有描述錯誤的地方,歡迎留言評論,我們一起學習呀~~

 

Biu~~~~~~~~~~~~~~~~~~~~宫å´éªé¾ç«è¡¨æå|é¾ç«gifå¾è¡¨æåä¸è½½å¾ç~~~~~~~~~~~~~~~~~~~~~~pia!

參考文章:《深入理解Java虛擬機器》周志明著

https://www.cnblogs.com/manayi/p/9290490.html

https://blog.csdn.net/youngyouth/article/details/79868299