1. 程式人生 > >jvm特性與原理---------->jvm運行時數據區分區

jvm特性與原理---------->jvm運行時數據區分區

程序計數器 如果 wid 實例 over 技術分享 xmx ins 調用

1.概述:

  • 內存分區:JVM會把自己所管理的所有內存區域進行分區。
  • 各個區域的服務對象
  • 各個區域中分別存放了什麽內容
    • 存放的數據是如何創建的
    • 這些數據在各個區域中存放,存儲的布局是什麽樣的
    • 如何訪問存放在不同內存區域的數據
  • 各個區域的創建和銷毀時間
    • 隨著進程的啟動和結束而創建和銷毀
    • 隨著線程的啟動和結束而創建和銷毀  
  • 各個區域服務過程中可能產生的問題(異常)
    • 各個區域中可能產生的異常
    • 如何解決上述各種異常  

2.JVM內存分區

  • 技術分享

3.JVM內存各個區域的比較

JVM內存分區(JVM運行時數據區)

所有線程共享的數據區

(有效範圍:整個進程)

線程隔離的數據區(線程私有內存)

(有效範圍:單個獨立線程)

方法區

Method area

(又稱GC)

Heap

程序計數器

Program Counter Register

虛擬機棧

VM Stack

本地方法棧

Native Method Stack

概述

  • 方法區可以被垃圾回收器管理,也可以不被垃圾回收器管理


  • 該部分內存是垃圾收集器管理的主要區域
  • 堆還可以細分為新生代和老年代
  • 或者分為Eden空間+From Survivor+ToSurvivor空間
  • Java堆既可以是固定的大小,也可以設置成可擴展的,通過-Xmx和-Xms控制
類似於虛擬機棧
  • 和虛擬機棧唯一的不同之處在於虛擬機棧服務於java方法,而本地方法棧服務於Native方法
  • Native方法可以是任何語言(如C、Python、shell程序)
  • 有些虛擬機中直接將本地方法棧和虛擬機棧合二為一

占用內存空間大小

  • 方法區可以是固定的大小;也可以設置成可擴展的
  • 方法區內存可以出於物理上不連續的空間中
  • 對於大多數具體應用而言,Heap是JVM所管理的內存中最大的一塊;
  • Java堆既可以是固定的大小,也可以設置成可擴展的,通過-Xmx和-Xms控制
  • Java堆可以處於物理上不連續的空間中,只要邏輯上是連續的即可

較小內存空間

和具體的java成員方法代碼有關;

和該線程包含的成員方法個數有關。

和具體的Native方法有關

Native方法可以是其他語言(Pythonshell)

服務對象

一個進程中的所有線程

(所有線程共享方法區)

一個進程中的所有線程

所有線程共享Heap區域內存

當前線程

(每條線程都有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲)

當前線程

每個線程都有自己獨有的該部分內存

當前線程

每個線程都有自己獨有的該部分內存

一個本地方法棧中有多個棧幀

Native method stack=棧幀1+棧幀2+...

每個棧幀服務於單個的Native方法

生命周期

Jvm啟動時創建

其生命周期與當前線程相同

其生命周期與當前線程相同

其生命周期與當前線程相同

存放內容

+

功能

v 存放內容:

概述:

方法區用於存儲已經被JVM加載的類信息+常量+靜態變量+即時編譯器編譯後的代碼

詳細解釋:

  1. 類信息=類的版本+字段+方法+接口等描述信息
  2. 運行時常量池=編譯期生成的各種字面量和符號引用(如類的符號引用)
  3. 編譯期間會將一些數據放入運行時常量池中,運行期間也可以把一些數據放入運行時常量池中,如Stringintern()方法

v 功能:

v 存放內容:

用於存放對象實例

用於存放所有數組

v 存放內容:

指示當前線程將要執行的下一條字節碼指令的行號

v 功能:

作為當前線程所執行的字節碼的行號指示器,字節碼解釋器工作時通過改變這個計數器的值來選取下一條需要執行的字節碼指令

v 存放內容:

虛擬機棧=棧幀1+棧幀2+....

線程中的每個java成員方法對應於一個棧幀

棧幀=局部變量表+操作數棧+動態鏈接+方法出口

局部變量表=8種基本數據類型+對象引用(地址)+returnAddress(一條字節碼指令的地址)

局部變量表是在編譯期間生成的

v 功能:

每個java成員方法執行過程中都會創建一個棧幀(stack frame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個java方法從調用到執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。

一個虛擬機棧中有多個棧幀(stack frame),

VM Stack=棧幀1+棧幀2+...

每個棧幀服務於單個的java方法。

v 存放內容:

v 功能:

每個Native成員方法執行過程中都會創建一個棧幀(stack frame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個Native方法從調用到執行完成的過程,就對應著一個棧幀在本地方法棧棧中入棧到出棧的過程。

各個區域存放的數據是如何創建的?

  • JVM中如何創建一個類對象實例?(僅限於普通java對象,不包括數組和class對象)

Step1JVM遇到一個new指令時,

Step2,先去“方法區”的運行時“常量池”中定位到一個類的符號引用,

Step3,檢查step2中找到的該類的符號引用所代表的實際的類是否已被加載、解析和初始化過,如果沒有則執行相應的“類的加載過程”

Step4,該類的加載檢查通過之後,JVM才開始為該類的新生對象分配內存(在heap內存中分配出一部分給新生對象)

至於具體的內存分配過程,則要看JVM所管理的堆內存空間的連續性是怎樣的:如果堆內存是規整的,則通過“指針碰撞”方法為新建對象分配內存;如果堆內存是不規整的,只能通過“空閑列表”分配堆內存空間,並且更新“空閑列表”

Step5,由step4可知,從JVM堆內存空間中分配一部分給new出的對象,其分配方法和堆內存空間是否規整有關,而堆內存空間是否規整又和垃圾回收機制有關。如果JVM中的垃圾回收期帶有壓縮整理功能,則堆內存空間是規整的,可以使用“指針碰撞”方法分配堆內存空間給new出的對象,否則就只能使用維護“空閑列表”的方法分配堆內存。

Step6,給new出的對象分配好堆內存空間之後,還要對該對象進行必要的設置(即為對象添加“對象頭”),如該對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡信息等。這些信息存放在對象的對象頭中。

Step7,執行init方法,初始化新建的對象。

Step8,將新生的對象的引用入棧(虛擬機棧)

  • 在堆內存空間分配內存給new出的對象時,容易出現線程安全問題,如何避免該類問題的發生?

方法一,為分配堆內存的操作加上同步處理

方法二,為單個線程分配獨立的堆內存空間TLAB(本地線程分配緩沖),然後執行每個線程時,在TLAB上分配內存給new出的對象

u

u

數據在這些區域存儲的布局是怎樣的?

  • 對象在heap內存中的存儲布局:

對象頭(Header+實例數據(Instance Data+對齊填充(padding

  1. 對象頭=對象自身的運行時數據+類型指針+(數組長度)

ü 對象自身的運行時數據=哈希碼+GC分代年齡+鎖狀態標誌+線程持有的鎖+偏向線程ID+偏向時間戳

ü 類型指針:這是一個指針,其中存儲了一個地址。這個地址指向JVM內存的“方法區”中的某個部分,“方法區”該部分內容存放的是該對象實例對應的類信息

ü (數組長度)

  1. 實例數據
  2. 對齊填充

u

u

u

如何使用存放在這些區域的數據?

u

  • 對象的訪問定位:
  1. Java程序要通過“虛擬機棧”上的“局部變量表”中的對象引用中存儲的對象實例的地址來操作JVM堆上存儲的實例對象。
  2. 通過上述reference數據訪問堆中對象的具體方式有兩種:使用句柄和直接指針。

使用句柄:堆內存-->句柄池;句柄=對象數據+指向類信息的指針

直接指針:沒有句柄池

u

u

u

服務過程中可能產生的異常

OutOfMemoryError:如果方法區中沒有足夠的內存存放相應的類信息或+常量+靜態變量+即時編譯器編譯數據,並且方法區再也無法擴展時,就會拋出該異常

OutOfMemoryError如果堆中沒有足夠的剩余內存來存放新的實例對象,並且堆也無法再擴展時,就會拋出該異常

不會產生異常

(這是JVM中唯一一個不會產生OutofMemoryError的區域)

u StackOverflowError如果線程請求的棧深度大於JVM所允許的深度,就拋出該異常

u OutOfMemoryError如果虛擬機棧可以動態擴展,但是在擴展時無法申請到足夠的內存,就會拋出該異常

u StackOverflowError如果線程請求的棧深度大於JVM所允許的深度,就拋出該異常

u OutOfMemoryError如果虛擬機棧可以動態擴展,但是在擴展時無法申請到足夠的內存,就會拋出該異常

jvm特性與原理---------->jvm運行時數據區分區