🔥JVM從入門到入土之JVM的運行時數據區

前言

文本已收錄至個人GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是如今
我知道不少人不玩qq了,可是懷舊一下,歡迎加入六脈神劍Java菜鳥學習羣,羣聊號碼:549684836 鼓勵你們在技術的路上寫博客java

絮叨

前面的基礎寫完了,接下來也是很重要的一部分,把數據加載到內存中,每種數據加載到哪一個位置呢?git

概述

對於 C C++ 來講,在內存管理領域,他們既擁有最高的權利的皇帝,可是同時他們又是從事最基礎工做的勞動人員,由於他們擔負着每個對象從開始到結束的維護責任,程序員

對於Java來講,再虛擬機自動內存管理的幫助下,再也不須要爲每個new操做去分配內存,不容易出現內存泄漏和內存溢出的狀況,可是由於咱們Java程序員 不用管理內存,因此一旦出現內存問題,很容易讓咱們手忙腳亂,因此呢咱們必需要了解Java虛擬器的內存管理機制,以便咱們能更好的處理各類各樣的問題github

運行時數據區

Java虛擬機在執行Java程序的過程當中會把所管理的內存劃分爲若干個不一樣的數據區域,這些區域都有各自的用途,以及建立和銷燬時間。再Java 1.8中 從宏觀上來講分爲線程共享,和線程私有 主要是分爲如下幾個區域算法

程序計數器

特色:線程內存獨享,佔用內存小,生命週期與線程相同(隨線程誕生而誕生,隨線程消亡而消亡)數組

功能:當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復(cpu在不斷輪詢執行任務)等基礎功能都須要依賴這個計數器來完成安全

異常:該區域沒有定義異常markdown

Java虛擬機棧

特色:先進後出,線程內存獨享,生命週期與線程相同多線程

單位:棧幀jvm

功能:已先進後出執行方法體的方法,執行完成的棧幀出棧

例子

虛擬機壓棧的過程

結論

  • 一個線程 表示的是一個Java虛擬機棧
  • 一個方法的執行,能夠經過壓棧的方式,也就是 方法對應的是棧幀

接下來咱們來談談棧的基本單位棧幀吧

棧幀(每個方法對應一個棧幀)

只有虛擬機棧頂的棧幀纔是有效的,稱爲當前棧幀 (Current Stack Frame),這個棧幀所關聯的方法稱爲當前方法(Current Method) 組成:

  • 局部變量表
  • 操做數棧
  • 動態連接
  • 方法出口信息
1.局部變量表:由基本數據類型和對象引用組成的

做用:用來存儲方法中的局部變量
基本單位:slot

  • 局部變量表的大小在編譯器就能夠肯定其大小了,所以在程序執行期間局部變量表的大小是不會改變的。
  • 若是存儲的是基本數據類型那麼直接存儲值
  • 若是存儲的是對象引用那麼存儲對象的引用地址( reference)(堆中)
補充:比較reference的兩種實現方式

直接引用 vs 使用句柄池

直接引用

reference直接指向對象,對象中指向對象類型數據

優勢:速度快,節約指針開銷。HotSpot採用的主要方式

使用句柄池:

java堆中會維護一個句柄池,句柄池分別指向對象實例(堆)的和對象類型數據(方法區)

優勢:對象移動後只需改變句柄池的指向地址,而不須要改變引用的指向地址。穩定

其實用白話來講 就是2我的是直接本身單線聯繫,仍是經過一個第三方聯繫,本身並不知道本身要聯繫的是誰,這個再抗戰特務劇中很常見呀。

2.操做數棧

操做數棧的深度在編譯器就能夠肯定其大小了,所以在程序執行期間局部變量表的大小是不會改變的。

功能:實現程序功能

3.動態鏈接

補充下直接引用與符號引用

  • 直接引用:當類已經加載到虛擬機時,經過地址直接調用該類
  • 符號引用(常量池中):在編譯的時候還不知道類是否被加載,先用符號代替該類,等實際運行時再用直接引用替換間接引用。

靜態解析:符號引用一部分會在類加載階段或第一次使用的時候轉化爲直接引用

動態鏈接: 將在每一次的運行期期間轉化爲直接引用

4.方法出口信息

當一個方法執行完畢以後,要返回以前調用它的地方,所以在棧幀中必須保存一個方法返回地址。

本地方法棧

  • 大致上都相似於虛擬機棧
  • 不一樣點:棧執行的java方法服務
  • 本地方法棧執行的是Native方法(不必定是用java開發的)服務

Java堆

特色:存儲對象,線程間內存共享,佔用大量內存,垃圾回收關注的重點區域

異常:OutOfMemoryError

每次都向堆中存放對象,方法結束後,銷燬棧幀的局部變量表時同時銷燬引用,該對象就成了可回收的垃圾。咋看起來沒什麼不對呀,但是仔細思考下仍是存在兩個問題 1.不斷的來回增長刪除對象,對於GC的工做量太大。 2.java使指針碰撞(堆中存入新對象的時候,指針根據對象大小移動到相應位置)來爲對象分配內存。若是在多線程的環境下,就會出現兩個對象同時移動當前前指針的狀況,形成線程不安全的狀況。

這裏就要引入TLAB的概念了

TLAB的全稱是Thread Local Allocation Buffer,這是一個線程專用的內存分配區域。每一個線程都會從Eden分配一塊空間,當線程銷燬時,咱們天然能夠回收掉TLAB的內存。

使用TLAB指令 -XX:UseTLAB

優勢:線程安全,減小垃圾回收的壓力。

缺點:TLAB空間大小是固定的,面對大對象的時候不夠靈活

方法區

特色:存儲類,線程間內存共享

存放已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據

異常:OutOfMemoryError

提到方法區不得不說的就是運行時常量池

補充:方法區不是永久代,只是Hotspot的實現方式而已。

遠行時常量池

運行時常量池是方法區的一部分,Class文件除了有類的版本,字段,方法,還有常量池

Java虛擬機對class文件每一部分的格式都有嚴格規定,每個字節用於存儲哪一種數據都必須符合規範纔會被jvm承認。但對於運行時常量池,Java虛擬機規範沒作任何細節要求。

運行時常量池有個重要特性是動態性,Java語言不要求常量必定只在編譯期才能產生,也就是並不是預置入class文件中常量池的內容才能進入方法區的運行時常量池,運行期間也有可能將新的常量放入池中,這種特性使用最多的是String類的intern()方法。

既然運行時常量池是方法區的一部分,天然受到方法區內存的限制。當常量池沒法再申請到內存時會拋出outOfMemeryError異常。

對象的建立

### 當虛擬機遇到一條New指令時:會進行以下步驟

  • 檢查指令的參數(即工做中咱們New的對象),可否在常量池中找到它的符號引用。
  • 若是存在,檢查符號引用表明的類是否被加載、解析、初始化過。(若是沒有則執行類的加載-----相關加載過程參考我前面的文章類加載機制)。
  • 加載經過後,虛擬機將爲新生對象分配內存。(所需內存大小在類加載完成後即可肯定)

兩種內存分配的方式:

  • 指針碰撞:假設Java堆中的內存是絕對規整的,全部用過的內存都放在一邊,空閒的內存放在另外一邊。中間放着一個指針做爲分界點的指示器,分配內存就僅僅是把指針往空閒空間那邊挪動一段與對象大小相等的距離。這種方式則屬於指針碰撞。

  • 空閒列表:若是堆中的內存並非規整的,已使用的內存和空閒內存相互交錯,顯然沒法使用指針碰撞。虛擬機就必須維護一個列表,記錄哪些內存是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新記錄表上的數據。這種方式屬於空閒列表。

具體選擇哪一種分配方式由Java堆決定,而Java堆是否規整,則有GC收集器決定。所以使用Serial、ParNew等帶Compact過程的收集器時,系統採用的分配算法是指針碰撞。而使用CMS這種基於Mark-Sweep算法的收集器時,一般採用的空閒列表。

如何保證分配內存時線程的安全性

  • 對分配內存的動做進行同步處理(實際上虛擬機採用CAS配上失敗重試的機制保證了更新操做的原子性)
  • 把分配內存的動做按照線程劃分在不一樣的空間之中進行(即每一個線程在Java堆中預先分配一小塊內存(稱爲本地線程分配緩衝))。

對象的內存佈局

在HotSpot虛擬機中,對象在內存中的佈局能夠分爲3塊區域:對象頭,實例數據和對齊填充

對象頭包括兩部分信息:

  • 存儲對象自身的運行時數據(如:哈希碼、GC分代年齡、鎖 等)
  • 類型指針(即對象指向他的類元數據的指針,虛擬機根據此指針來確認對象屬於哪一個類的實例)
  • 若是是數據 記錄數組的大小 實例數據:
  • 實例數據纔是對象真正存貯的有效信息(即程序中所定義的各類類型的字段內容)。

對齊填充:

  • 不是必然存在的,僅僅起到佔位符的做用,由於HotSpot虛擬機要求對象的起始地址必須是8個字節的整數倍。

來個例子把JVM的運行時的區域所有串起來

結尾

明天就是 咱們大頭 垃圾回收算法,和垃圾回收器了,而後咱們再搞幾個實戰,對於JVM也算是有一個基礎的認識了,以後就要靠你們本身多去累計實戰經驗了。

平常求贊

好了各位,以上就是這篇文章的所有內容了,能看到這裏的人呀,都是真粉

創做不易,各位的支持和承認,就是我創做的最大動力,咱們下篇文章見

六脈神劍 | 文 【原創】若是本篇博客有任何錯誤,請批評指教,不勝感激 !

相關文章
相關標籤/搜索