JAVA-大白話探索JVM-運行時內存(三)

前面章節

JAVA-大白話探索JVM-類加載器(一) JAVA-大白話探索JVM-類加載過程(二)html

JVM運行時內存

經過以前的章節,咱們知道.class類如何加載到內存中,如圖紅框

image.png

開始講講內存空間

先了解JVM的週期

  1. JVM在java程序執行時運行,結束時中止。
  2. 一個java程序對應開啓一個JVM進程
  3. JVM的線程分爲兩種:守護線程和普通線程
    • 守護線程屬於JVM本身使用的線程,如GC
    • 普通線程是java程序的線程

線程私有數據區

  • Java棧(VM Stack)
  • 本地方法棧(NM Stack)
  • 程序計數器及隱含寄存器(Program Counter Register)

線程共享數據區

  • 方法區(Method Area)
  • Java堆(Heap)
  • 執行引擎
  • 本地方法接口
  • 本地方法庫

你會發現,這都是些什麼?????。。。。。。呃java

不着急,一步一步來算法

首先,就是你了,方法區(Method Area,線程共享)

  1. 類的結構信息和類靜態變量都保存在方法區(這樣說會不會很抽象,舉個例,例如運行時常量池,成員變量和方法數據,構造函數和普通函數的字節碼內容,還包括一些在類、實例、接口初始化時用到的特殊方法。開發人員在程序中經過Class對象中的getName、isInstance等方法獲取信息時,這些數據都來自方法區。)
  2. 程序中的全部線程共享一個方法區,簡稱全局共享
  3. 對於HotSpot虛擬機,方法區對應爲永久代(Permanent Generation),但本質上,二者並不等價,僅僅是由於HotSpot虛擬機的設計團隊是用永久代來實現方法區而已,對於其餘的虛擬機(JRockit、J9)來講,是不存在永久代這一律唸的。
  4. 使用永久代來實現方法區並非一個好注意,因爲方法區會存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等,在某些場景下很是容易出現永久代內存溢出。如Spring、Hibernate等框架在對類進行加強時,都會使用到CGLib這類字節碼技術,加強的類越多,就須要越大的方法區來保證動態生成的Class能夠加載入內存。在JSP頁面較多的狀況下,也會出現一樣的問題。
  5. 在JDK1.8下並無出現咱們指望的永久代內存溢出錯誤,而是Metaspace內存溢出錯誤。這是由於Java團隊從JDK1.7開始就逐漸移除了永久代,到JDK1.8時,永久代已經被Metaspace取代,所以在JDK1.8並無出現咱們指望的永久代內存溢出錯誤。在JDK1.8中,JVM參數-XX:PermSize和-XX:MaxPermSize已經失效,取而代之的是-XX:MetaspaceSize和XX:MaxMetaspaceSize。注意:Metaspace已經再也不使用堆空間,轉而使用Native Memory
  6. 還有一點須要說明的是,在JDK1.6中,方法區雖然被稱爲永久代,但並不意味着這些對象真的可以永久存在了,JVM的內存回收機制,仍然會對這一塊區域進行掃描,即便回收這部份內存的條件至關苛刻。

呃。。。。。。。有點多,慢慢吸取,這方法區也須要好好琢磨琢磨,一不當心溢出就麻煩了。緩存

其次,Java堆(Heap,線程共享)

  1. Java堆是JVM所管理的最大一塊內存,全部線程共享這塊內存區域,幾乎全部的對象實例都在這裏分配內存,所以,它也是垃圾收集器管理的主要區域。
  2. 從內存回收的角度來看,因爲如今的收集器基本都採用分代收集算法,因此Java堆又能夠細分紅:新生代和老年代,新生代裏面有分爲:Eden空間、From Survivor空間、To Survivor空間。
  3. 有一點須要注意:Java堆空間只是在邏輯上是連續的,在物理上並不必定是連續的內存空間。
  4. 默認狀況下,新生代中Eden空間與Survivor空間的比例是8:1,可使用參數-XX:SurvivorRatio對其進行配置。大多數狀況下,新生對象在新生代Eden區中分配,當Eden區沒有足夠的空間進行分配時,則觸發一次Minor GC,將對象Copy到Survivor區,若是Survivor區沒有足夠的空間來容納,則會經過分配擔保機制提早轉移到老年代去。
  5. 何爲分配擔保機制?在發送Minor GC前,JVM會檢查老年代最大可用的連續空間是否大於新生代全部對象的總空間,若是是,那麼能夠確保Minor GC是安全的,若是不是,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若是小於,直接進行Full GC,若是大於,將嘗試着進行一次Minor GC,Minor GC失敗纔會觸發Full GC。注:不一樣版本的JDK,流程略有不一樣。
  6. Survivor區做爲Eden區和老年代的緩衝區域,常規狀況下,在Survivor區的對象通過若干次垃圾回收仍然存活的話,纔會被轉移到老年代。JVM經過這種方式,將大部分命短的對象放在一塊兒,將少數命長的對象放在一塊兒,分別採起不一樣的回收策略。

Java棧(Stack,線程私有)、本地方法棧

Java棧

  1. java棧中只保存基礎數據類型(四類八種)和自定義對象引用
  2. 存取類型:先進後出
  3. 棧內數據在超出其做用域將自動釋放
  4. 每一個棧是線程私有,它們的生命週期與線程相同。
  5. 每一個線程創建一個操做棧,每一個棧又包含若干個棧幀,每一個棧幀對應每一個方法調用
  6. 棧幀:
    • 局部變量區(方法內基本類型變量、變量對象指針)
    • 操做數棧區(存放方法執行過程當中產生的結果)
    • 運行環境區(動態連接、方法返回相關信息、異常捕捉)

本地方法棧

  1. 與JAVA棧相似
  2. 本地方法棧是在程序調用或JVM調用本地方法接口(Native)時候啓用
  3. 本地方法非java語言編寫,不受JVM管理
  4. HotSpot VM將本地方法棧和JVM棧合併了。

程序計數器(線程私有)

概念:在JVM概念模型裏,字節碼解釋器工做時就說經過改變這個計算器的值來選取下一條須要執行的字節碼指令。分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。安全

  1. Java虛擬機能夠支持多條線程同時執行,多線程是經過線程輪流切換來得到CPU執行時間的,每條線程都會有獨立的程序計數器
  2. 若是執行java方法,程序計數器記錄JVM字節碼指令的地址,若是執行 native,計數器爲空(Underfined)
  3. 程序計數器這個內存區域在JVM規範中是惟一沒有規定任何OutOfMemoryError的區域

運行時常量池(Runtime Constant Pool)

  1. 方法區的一部分,用於存放編譯期間生成的各類字面量(int,short等等)和符號引用(對象符號引用Integer,String)
  2. 除了編譯產生能存入,運行期間也能將新的常量放入池中(String.intern())
  3. 節省內存空間:常量池中若是有對應的字符串,那麼則返回該對象的引用,從而沒必要再次建立一個新對象。
  4. 節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,==判斷引用是否相等,也就能夠判斷實際值是否相等
  5. Byte、Short、Integer、Long、Character這5種包裝類都默認建立了數值[-128 , 127]的緩存數據。當對這5個類型的數據不在這個區間內的時候,將會去建立新的對象,而且不會將這些新的對象放入常量池中。
  6. Oracle對Java 7中的常量池作了一個很是重要的改變 — 常量池被從新定位到堆中。這意味着你再也不受限於單獨的固定大小內存區域。全部字符串如今都位於堆中,與大多數其餘普通對象同樣,這使你能夠在調整應用程序時僅管理堆大小。

完了······

先暫時這麼多吧,以上是我我的針對JVM的總結,也方便你們快速理解跟鞏固,有錯誤的地方望告知下,謝謝。

版權聲明:本文爲不會代碼的小白原創文章,轉載需添加小白地址 :https://www.ccode.live/bertonlee/list/12?from=art

相關文章
相關標籤/搜索