JVM(二)-內存區域之線程私有區域

概述:

  對於從事C、C++開發的程序員來講,在內存管理領域,他們既是擁有最高權力的「皇帝」,又是從事最基礎工做的勞動人民——既擁有每一個對象的「全部權」,html

又擔負着每個對象從開始到終結的維護職責。java

  對於java程序員來講,在虛擬機自動內存管理機制的幫助下,再也不須要爲沒一個new操做去配對的free/delete(C、C++語言對對象的刪除和內存釋放操做),linux

不容易出現內存泄漏和內存溢出問題,看起來由虛擬機管理內存一切看起來很美好。不過,也正是java把控制內存的權力交給了java虛擬機,一旦出現內存泄漏程序員

和內存溢出方面的問題,若是不瞭解虛擬機是怎麼使用內存的,那排查錯誤、修正問題將會成爲一項異常艱難的工做。windows

運行時數據區:

  java虛擬機在執行java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域有各自的用途,以及建立和銷燬的時間,有些區域會隨着緩存

虛擬機進程的啓動而一直存在,有些區域則是依賴用戶線程的啓動和結束而創建和銷燬。以下圖所示:多線程

咱們知道JVM也屬於一種特殊的操做系統,那這些數據區域跟咱們最經常使用的windows哪些部分相對應呢。咱們能夠吧windows的CPU+緩存+主內存和JVM的執行引擎+oracle

操做數棧+(棧、堆)對應起來,這樣更加利於咱們去理解JVM。jvm

虛擬機棧:

  從上圖可見,java虛擬機棧是線程私有的,它的生命週期和線程相同。虛擬機棧描述的是java方法執行的線程內存模型:每一個方法被執行的時候,java虛擬機都會spa

同步建立一個棧幀用於存儲局部變量表、操做數棧、動態鏈接、返回地址等信息。每個方法被調用直至執行完畢的過程,就對應着一個棧幀在虛擬機棧中從入棧到

出棧的過程。咱們來經過一段很是簡短的代碼來演示虛擬機棧的做用:

/**
 * @ClassName StackTest
 * @description:
 * @author:liuyi
 * @Date:2020/11/23 23:45
 */
public class StackTest {

    public static void main(String[] args) {
        A();
    }

    static void A(){
        B();
    }

    static void B(){
        C();
    }

    static void C(){

    }
}

  當咱們運行main方法,虛擬機會開啓一個線程,同時爲當前線程劃分一塊內存區域做爲當前線程的虛擬機棧。同時在執行每一個方法的時候都會打包成一個棧幀。

好比 main 開始運行,打包一個棧幀送入到虛擬機棧。C 方法運行完了,C 方法出棧,接着 B 方法運行完了,B 方法出棧、接着 A 方法運行完了,A 方法出棧,

最後 main 方法運行完了,main 方法這個棧幀就出棧了。這個就是 Java 方法運行對虛擬機棧的一個影響。虛擬機棧就是用來存儲線程運行方法中的數據的。而

每個方法對應一個棧幀。入棧過程以下圖所示:

 上圖描述了整個main方法調用的入棧和出棧的過程,須要注意的是棧幀出棧以後就沒了,棧幀沒得GC的說法。

棧幀詳解:

  棧幀大致都包含四個區域:(局部變量表、操做數棧、動態鏈接、返回地址)

  • 局部變量表:顧名思義就是局部變量的表,用於存放咱們的局部變量的(方法中的變量)。首先它是一個 32 位的長度,主要存放咱們的 Java 的八大基礎數據
類型,通常 32 位就能夠存放下,若是是 64 位的就使用高低位佔用兩個也能夠存放下,若是是局部的一些對象,好比咱們的 Object 對象,咱們只須要存放它的一個引用地址便可。
  • 操做數棧:存放 java 方法執行的操做數的,它就是一個棧,先進後出的棧結構,操做數棧,就是用來操做的,操做的的元素能夠是任意的 java 數據類型,所
以咱們知道一個方法剛剛開始的時候,這個方法的操做數棧就是空的。操做數棧本質上是 JVM 執行引擎的一個工做區,也就是方法在執行,纔會對操做數棧進行操做,若是代碼不不執行,操做數棧其實就是空的。
  • 動態鏈接:Java 語言特性多態(後續章節細講,須要結合 class 與執行引擎一塊兒來說)。
  • 方法出口:正常返回(調用程序計數器中的地址做爲返回)、異常的話(經過異常處理器表<非棧幀中的>來肯定)。

咱們來經過分析一個簡單的方法來理解棧幀中各個區域是如何運做的,代碼以下:

/**
* @ClassName User
* @description:
* @author:liuyi
* @Date:2020/11/25 20:51
*/
public class User {
public static int work(){
int a = 2;
int b = 3;
int c = a*b;
return c;
}

public static void main(String[] args) {
System.out.println(work());
}
}

 當該程序運行的時候,JVM會爲其分配虛擬機棧,並生成對應的棧幀,以下圖所示:

咱們經過反彙編命令查看work方法的字節碼以下:

咱們看到work方法一共由10條字節碼組成,咱們來逐步分析。

打開 https://cloud.tencent.com/developer/article/1333540查看字節碼指令

現來看iconst_2對應的含義,如圖

 因此第1個字節碼是將一個值爲2的數字加載到操做數棧。再來看 istore_0的含義,如圖

 

  因此第2個字節碼的含義就是將第一步中放入到操做數棧的數字放到局部變量表中,位置爲0。因此前面兩個字節碼對應的java代碼就是int a = 2;那麼顯而易見3和4兩個字節碼對應的

就是int b = 3;到這裏,你們內心確定會有疑問,爲何不直接將值放到局部變量表呢?咱們接着分析,你就明白了。

  繼續來看第5和第6兩個字節碼:iload_0和iload_1,它們的含義是將局部變量表中位置0和1的兩個數加載到操做數棧中,接着咱們來看關鍵的第7個字節碼:imul,它表明的意思

是相乘,就是將操做數棧中的數字進行乘法運算,咱們知道相乘是須要運算的,因此此時要交給執行引擎運算,運算完成以後再將運算的結果返回到操做數棧。因此操做數棧的做用

就是爲jvm高速的計算提供緩衝區。

  接着來看第8個字節碼:istore_2,它的含義就是將計算的結果放入局部變量表,到這裏int c = a*b;就執行完了。而後再來看第9和第10個字節碼,它們的含義是將局部變量表的值再

壓入操做數棧,最後返回。至此,整個方法執行結束,以上就是棧幀中各個區域在方法執行中的運做流程。

 虛擬機棧大小的設置:

   虛擬機棧的大小缺省爲 1M,可用參數 –Xss 調整大小,例如-Xss256k。

參數官方文檔(JDK1.8):https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html。

 

 咱們能夠看到linux的建議配置爲1M,至於windows爲啥沒有,博主大膽猜測可能跟微軟和Oracel兩家公司競爭有關吧,畢竟微軟開發.net就是和java競爭的。

虛擬機棧相關的程序異常:

  • StackOverflowError異常:若是線程請求的棧深刻大於虛擬機所容許的深度,將拋出StackOverflowError異常,一般是由無線遞歸致使的,以下面的代碼

 

  •  OutOfMemoryError:若是java虛擬機的容量能夠動態擴展,當棧擴展時沒法申請到足夠的內存會拋出OutOfMemoryError異常。這種狀況基本不多出現,也很難模擬,這裏就不演示了。

程序計數器:

  與虛擬機棧同樣,程序計數器也是線程私有的。程序計數器是一塊很小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器,就如上面反彙編User.class看到的同樣。每個字節碼都有本身的序號:

 

   如上圖所示,雖然這些序號是由順序的,可是並不必定是依次遞增,若是某給字節碼佔用的空間很大,那麼它的序號相較於前一個序號就差距更大。

在java虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,它是程序控制流的執行器,分支、

循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器完成。

  它還有另一個做用,咱們知道在java中能夠開啓成百上千個線程,可是咱們通常的電腦CPU也就8個左右。java虛擬機的多線程是經過線程輪流切換、

分配處理器執行時間方式來實現的,那麼切換後虛擬機是怎麼知道之前運行的位置,繼續運行的呢?這個時候,程序計數器就起到了決定性的做用,由於

程序計數器是線程獨有的,因此不會相互影響,當切回到當前線程,根據程序計數器記錄的序號,繼續執行對應的字節碼便可。

  在JVM中,只有執行java方法的時候,程序計數器纔會記錄正在執行的虛擬機字節碼指令的地址,若是正在執行的是本地(Native)方法,這個計數器

則應爲空(Undefined)。可是這裏會產生一個疑問,若是恰好在執行Native方法的時候線程切換了,那切回來以後該怎麼找到對應的位置呢?這裏,我猜想

JVM可能規定了 在執行Native本地方法的時候,禁止切換當前線程(如不正確,請指正)。xianc

本地方法棧:

  本地方法棧與虛擬機棧的做用很是類似,其區別只是虛擬機棧爲java方法服務,而本地方法棧專門爲Native本地方法服務。須要注意的是,HotSpot直接把

本地方法棧和虛擬機棧合併了。

總結:

  本篇文章介紹了JVM的內存區域之線程私有區域,主要介紹了虛擬機棧的各個組成部分以及java方法是怎麼經過虛擬機棧來實現執行的,接着介紹了程序計數器的做用

最後簡述了本地方法棧。下一章,咱們將要分析JVM內存區域的線程共享數據區,主要包括堆、方法區、運行時常量池以及直接內存等內容。

相關文章
相關標籤/搜索