JVM內存區域之線程私有區域

2020-12-17 計算機java編程

概述:

對於從事C、C++開發的程式設計師來說,在內存管理領域,他們既是擁有最高權力的「皇帝」,又是從事最基礎工作的勞動人民——既擁有每個對象的「所有權」,

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

對於java程式設計師來說,在虛擬機自動內存管理機制的幫助下,不再需要為沒一個new操作去配對的free/delete(C、C++語言對對象的刪除和內存釋放操作),

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

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

運行時數據區:

java虛擬機在執行java程序的過程中會把它所管理的內存劃分為若干個不同的數據區域。這些區域有各自的用途,以及創建和銷毀的時間,有些區域會隨著

虛擬機進程的啟動而一直存在,有些區域則是依賴用戶線程的啟動和結束而建立和銷毀。如下圖所示:

我們知道JVM也屬於一種特殊的作業系統,那這些數據區域跟我們最常用的windows哪些部分相對應呢。我們可以把windows的CPU+緩存+主內存和JVM的執行引擎+

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

虛擬機棧:

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

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

出棧的過程。我們來通過一段非常簡短的代碼來演示虛擬機棧的作用:

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

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

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

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

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

棧幀詳解:

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

局部變量表:顧名思義就是局部變量的表,用於存放我們的局部變量的(方法中的變量)。首先它是一個 32 位的長度,主要存放我們的 Java 的八大基礎數據

類型,一般 32 位就可以存放下,如果是 64 位的就使用高低位佔用兩個也可以存放下,如果是局部的一些對象,比如我們的 Object 對象,我們只需要存放它的一個引用地址即可。

操作數棧:存放 java 方法執行的操作數的,它就是一個棧,先進後出的棧結構,操作數棧,就是用來操作的,操作的的元素可以是任意的 java 數據類型,所

以我們知道一個方法剛剛開始的時候,這個方法的操作數棧就是空的。操作數棧本質上是 JVM 執行引擎的一個工作區,也就是方法在執行,才會對操作數棧進行操作,如果代碼不不執行,操作數棧其實就是空的。

動態連接:Java 語言特性多態(後續章節細講,需要結合 class 與執行引擎一起來講)。

方法出口:正常返回(調用程序計數器中的地址作為返回)、異常的話(通過異常處理器表<非棧幀中的>來確定)。

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

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

我們通過反彙編命令查看work方法的字節碼如下:

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

現來看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。

我們可以看到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內存區域的線程共享數據區,主要包括堆、方法區、運行時常量池以及直接內存等內容。

相關焦點

  • JVM中的五大內存區域劃分詳解及快速掃盲
    運行時數據區域組成虛擬機在執行java程序時,會將自己管理的內存劃分為幾個區域,每個區域都有自己的用途,並且創建時間和銷毀時間也不一樣。在程序運行時的內存區域主要可以劃分為五個,分別是:方法區、堆、虛擬機棧、本地方法棧、程序計數器。可以用下面的圖來描述:2.
  • java線程前傳——jvm內存結構、內存模型和cpu結構
    ,這個過程是少不了的一個線程肯定是要運行在一個核上的,多個線程可以運行在不同的核上,這個時候,因為緩存的存在,如果沒有同步機制,那一個線程修改了緩存的數據,另一個線程也修改了緩存的數據,這個時候這兩個線程修改後的數據都需要寫入到內存當中,就會出現問題jvm為了方便,將這些緩存抽象出來,構造了自己的內存模型,即主內存和工作內存的數據交互,即java 內存模型(jmm)
  • JVM-概述和內存區域
    舉個例子將groovy編譯之後的class文件用jvm運行1、先配置好groovy環境方法區和堆區是所有線程共享的內存區域;Java棧又叫做jvm虛擬機棧。執行引擎等同於翻譯class文件的語言翻譯器。
  • 淺析JVM內存模型:虛擬機如何實現多線程而導致的並發問題
    由於 JVM運行程序的實體是線程,而每個線程創建時JVM都會為其創建一個工作內存(有些地方稱為棧空間),用於存儲線 程私有的數據,而Java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可以訪問, 但線程對變量的操作(讀取賦值等)必須在工作內存中進行,首先要將變量從主內存拷貝的自己的工作內存空間,然 後對變量進行操作,操作完成後再將變量寫回主內存,不能直接操作主內存中的變量
  • JVM、GC 大串講,面試夠用了
    java虛擬機的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,任何一個確定的時刻,一個處理器都只執行一條線程中的指令。因此,為了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲。所以這類內存區域為「線程私有」的內存。
  • 程式設計師每日一題-jvm裡方法和方法區、棧區的二三事
    JAVA虛擬機運行時數據區的邏輯圖好,對照上圖,逐項解釋A:堆區是JVM 所管理的內存中最大的一塊。線程共享,主要是存放對象實例和數組。堆區是jvm裡面最需要深入研究的一塊區域,這裡面涉及內存分配,區域劃分,對象信息,垃圾回收。可以說如果java程式設計師對堆區不熟悉,那麼一定寫不出好的代碼。本文暫時不深入討論,後續會開專題深入講解。B:棧區,也叫虛擬機棧,顧名思義,它是一個棧,先進後出。它是線程創建時跟著創建,生命周期和線程一致,是線程私有的。
  • 想理解JVM看了這篇文章,就知道了!
    本次將對jvm有更深入的學習,我們不僅要讓程序能跑起來,而且是可以跑的更快!可以分析解決在生產環境中所遇到的各種「棘手」的問題,比如運行的應用卡住了,日誌不輸出,程序沒有反應,CPU負載突然升高,多線程應用下,如何分配線程數量等。
  • 從零開始了解多線程知識之開始篇目——jvm&volatile
    ,前面說過,工作內存是每個線程的私有數據區域,因此不同的線程間無法訪問對方的工作內存,線程間的通信(傳值)必須通過主內存來完成。JMM不同於JVM內存區域模型 (JVM是是實際存在的,JMM只是邏輯規則)JMM與JVM內存區域的劃分是不同的概念層次,更恰當說JMM描述的是一組規則,通過這組規則控制程序中各個變量在共享數據區域和私有數據區域的訪問方式,JMM是圍繞原子性,有序性、可見性展開。
  • 一篇文章全面吃透Java虛擬機,線程安全的實現方法
    內存空間小,線程私有.它可以看做是當前線程所執行的字節碼的行號指示器.也就是說,線程主要是執行任務,而執行到哪裡,需要使用程序計數器來記錄.字節碼解釋器工作是就是通過改變這個計數器的值來選取下一條需要執行指令的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴計數器完成.
  • 聊到JVM(還怕面試官問JVM嗎?)
    9、PC寄存器  程序計數器:Program Counter Register10、方法區 方法區是被所有線程共享,所有欄位和方法字節碼,以及一些特殊方法,如構造函數,接口代碼也在此定義,簡單說,所有定義的方法的信息都保存在該區域,此區域屬於共享區間;方法區與Java堆一樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
  • jvm面試系列一:java內存模型你掌握了多少?
    今天我們就來聊一聊Java內存模型,面試中面試官會通過考察你對jvm的理解更深入得了解你的水平。
  • 這一定是全網寫JVM最好的文章之一—JVM運行時數據區
    Java內存區域根據上圖可以知道,方法區和堆是一個顏色,虛擬機棧和本地方法棧和程序計數器是一個顏色 所以,方法區和堆是線程共享的,虛擬機棧和本地方法棧和程序計數器是線程私有的通過上圖,可知Java內存區域包括運行時數據區和執行引擎和本地庫接口和本地方法庫運行時數據區
  • 13道 常見的JVM 面試題
    主內存:java虛擬機規定所有的變量(不是程序中的變量)都必須在主內存中產生,為了方便理解,可以認為是堆區。工作內存:java虛擬機中每個線程都有自己的工作內存,該內存是線程私有的為了方便理解,可以認為是虛擬機棧。
  • JVM性能調優實踐
    ③GC日誌:程序啟動時用 -XX:+PrintGCDetails 和 -Xloggc:/data/jvm/gc.log 可以在程序運行時把gc的詳細過程記錄下來,或者直接配置「-verbose:gc」參數把gc日誌列印到控制臺,通過記錄的gc日誌可以分析每塊內存區域gc的頻率、時間等,從而發現問題,進行有針對性的優化。
  • 優雅終止線程?系統內存佔用較高?
    其次,我用了jcmd命令去對對外內存進行分析,排斥了堆外內存洩露的問題然後接下來就是用jstack命令查看jvm進程的各個線程都是什麼樣的狀態。與job有關的幾個線程全部是waiting on condition狀態(線程結束,線程池將他們掛起的)。
  • java垃圾回收以及jvm參數調優概述
    和ParNew區別 5.6  jvm常用監控工具 Java技術體系中所提倡的自動內存管理最終可以歸結為自動化地解決了兩個問題:給對象分配內存以及回收分配給對象的內存。而且這兩個問題針對的內存區域就是Java內存模型中的堆。垃圾回收機制的引入可以有效的防止內存洩露、保證內存的有效使用,也大大解放了Java程式設計師的雙手,使得他們在編寫程序的時候不再需要考慮內存管理。本文主要對java垃圾回收機制以及jvm參數等方面做個綜述,也算是自己做開發這幾年對這方面的一個總結。
  • JVM超神之路:年後跳槽需要的JVM知識點,周末給你整理了一份!!!
    6、系統內存飆高,如何查找問題?JDK內置的命令行:jps(查看jvm進程信息)、jstat(監視jvm運行狀態的,比如gc情況、jvm內存情況、類加載情況等)、jinfo(查看jvm參數的,也可動態調整)、jmap(生成dump文件的,在dump的時候會影響線上服務)、jhat(分析dump的,但是一般都將dump導出放到mat上分析)、jstack(查看線程的)。
  • JVM(堆、方法區、棧、這些基礎您知道了嗎)
    JVM 內存區域1.線程分配緩衝區(TLAB)JVM是被線程共享的,而且不斷頻繁的產生新的對象,那麼在並發情況下,為保證線程安全需要加鎖,但是這樣對象的分配效率又會大大折扣,因此TLAB出現,每個線程都會分配一塊獨立的TLAB空間(線程私有)其大小由JVM根據運行的情況計算而得
  • JVM內存分析,程式設計師進階的必經之路
    其中方法棧裡的線程是不共享的,什麼意思呢?最直接地理解就是:棧裡面,不同的線程是獨立存在的,所以線程安全。相反的是堆、方法區線程是共享的,這又是什麼意思呢?最直接地理解就是:堆、方法區裡面的數據,不同的線程都可以拿過去用,所以線程不安全。
  • 談談Java內存管理
    對於Java來說,最終是要依靠字節碼運行在jvm上的。目前,常見的jvm有以下幾種:Sun HotSpotBEA JrockitIBM J9Dalvik(Android)其中以HotSpot應用最廣泛。