JVM 概述

Java Virtual Machine 是Java平臺(tái)的基石,包含相應(yīng)的技術(shù)規(guī)范、實(shí)現(xiàn)(JVM的實(shí)現(xiàn)就是JRE)、運(yùn)行實(shí)例
為了實(shí)現(xiàn)一次編寫到處運(yùn)行 (Write Once Run Anywhere)的理念,JVM是和平臺(tái)相關(guān)的

Java HotSpot Client VM (client VM) :主要用于減少啟動(dòng)時(shí)間和內(nèi)存占用,啟動(dòng)應(yīng)用時(shí)使用-client指定
Java HotSpot Server VM (server VM):用于最大化的執(zhí)行速度,啟動(dòng)應(yīng)用時(shí)使用-server指定

JVM主要包括三個(gè)子系統(tǒng)

  • 類加載子系統(tǒng) Class Loader
  • 運(yùn)行時(shí)數(shù)據(jù)區(qū) Runtime Data Area
  • 執(zhí)行引擎 Execution Engine
JVM構(gòu)架

運(yùn)行時(shí)數(shù)據(jù)區(qū)

JVM定義了不同的運(yùn)行時(shí)數(shù)據(jù)區(qū),一些是在JVM啟動(dòng)時(shí)就創(chuàng)建的,有些數(shù)據(jù)區(qū)的生命周期和線程有關(guān),主要有5種

程序計(jì)數(shù)器 Progarm Counter Registers

JVM支持許多線程同時(shí)運(yùn)行,每個(gè)JVM線程都有自己的pc寄存器
當(dāng)線程運(yùn)行的是Java方法時(shí),存儲(chǔ)當(dāng)前正在執(zhí)行的JVM指令地址
如果是Native方法,未定義

Java虛擬機(jī)棧 Java Virtual Machine Stacks

每個(gè)線程都有一個(gè)私有的Java虛擬機(jī)棧,和線程的生命周期相同。描述了Java方法執(zhí)行的內(nèi)存模型
用來存儲(chǔ)棧幀(Frame)

Frame
每個(gè)方法在調(diào)用的時(shí)候,都會(huì)創(chuàng)建一個(gè)棧幀,包含局部變量(Local Variables)、操作數(shù)棧(Operand Stacks)、動(dòng)態(tài)鏈接(Dynamic Linking)、方法的調(diào)用者信息(Normal/Abrupt Method Invocation Completion)
局部變量
存儲(chǔ)boolen,byte,char,short,int,long,float,double,reference(引用),returnAddress(指向一條操作碼)
局部變量的數(shù)組長度是在編譯時(shí)確定的,之后不再改變
同樣用于方法調(diào)用中的參數(shù)傳遞,0號(hào)索引用于存儲(chǔ)this
操作數(shù)棧
深度由編譯時(shí)確定
加載常量,局部變量或者字段到操作數(shù)棧,出棧計(jì)算出結(jié)果,壓棧
還可以用來準(zhǔn)備傳遞到函數(shù)中的參數(shù),接收方法的返回值

和常見的語言(例如C)的棧類似:存儲(chǔ)局部變量,局部結(jié)果,用于方法調(diào)用和返回等

Exception
StackOverflowError:線程請(qǐng)求的棧深度大于JVM的的允許范圍(函數(shù)調(diào)用層級(jí)過多導(dǎo)致)
OutOfMemoryError:內(nèi)存不足,無法動(dòng)態(tài)擴(kuò)展內(nèi)存時(shí)

-Xss<size>:設(shè)定線程棧的大小 (默認(rèn)單位是byte,支持 K/k M/m G/g 作為單位)

本地方法棧 Native Method Stack

JVM使用到Native方法的時(shí)候使用本地方法棧
每一個(gè)線程,將創(chuàng)建一個(gè)單獨(dú)的本地方法棧。

和Java虛擬機(jī)棧的異常相同

堆Heap

所有的JVM線程共享,所有的類對(duì)象和數(shù)組都要在堆上分配
堆是虛擬機(jī)所管理的內(nèi)存中最大的一塊,也是Garbage Collector管理的主要區(qū)域
Java堆的內(nèi)部可以不連續(xù),堆空間大小可以是固定的,也可以是可擴(kuò)展收縮的

Exception:
OutOfMemoryError: 請(qǐng)求更多的堆空間無法滿足

-Xms<size>:初始堆大小
-Xmx<size>:最大堆大小

方法區(qū) Method Area

被所有Java線程共享,存儲(chǔ)被虛擬機(jī)加載的類信息,例如運(yùn)行時(shí)常量池,字段和方法數(shù)據(jù),方法的代碼,構(gòu)造函數(shù)等,JVM規(guī)范沒有限定方法區(qū)的位置,可以是固定大小或者可擴(kuò)展,內(nèi)存不要求連續(xù)

Hotspot虛擬機(jī)中的方法區(qū)之前也叫做永久代,PermGen,Java8 后叫做元空間,Matespace,分配在本地內(nèi)存Native Memory中,它的大小可以自動(dòng)的增長,可以通過XX:MaxPermSize,-XX:MaxMetaspaceSize=<size>設(shè)定大小
因?yàn)槭窃诒镜貎?nèi)存(native memory)分配,所以其最大可利用空間是整個(gè)系統(tǒng)內(nèi)存的可用空間,不容易遇到OutOfMemoryError錯(cuò)誤

運(yùn)行時(shí)常量池 Runtime Constant Pool

是方法區(qū)的一部分,類似于常規(guī)語言的符號(hào)表
類或者接口的.class文件中,有一個(gè)常量表屬性(constant_pool table),存放各種字面常量(在編譯時(shí)確認(rèn))和字段引用(在運(yùn)行時(shí)確認(rèn)),加載到虛擬機(jī)時(shí),對(duì)應(yīng)加載到運(yùn)行時(shí)常量池

Exception:
OutOfMemoryError: 方法區(qū)無法滿足內(nèi)存分配需求

堆外內(nèi)存

Native Memory或者Off-Heap內(nèi)存空間,例如NIO中的DirectBuffer就是使用native函數(shù)直接分配的堆外內(nèi)存,可以通過-XX:MaxDirectMemorySize來設(shè)置NIO直接緩沖區(qū)的最大值。

使用堆外內(nèi)存的好處:

  • 可以擴(kuò)展更大的內(nèi)存空間
  • 能減少GC時(shí)間
  • 可以在進(jìn)程間共享數(shù)據(jù)

Java堆中對(duì)象的分配、布局、訪問

對(duì)象的創(chuàng)建

通過new指令創(chuàng)建對(duì)象時(shí),虛擬機(jī)的執(zhí)行流程

  1. 類加載
  2. 為對(duì)象分配內(nèi)存(大小是確定的,在加載后便已經(jīng)確定),兩種分配策略
  • 指針碰撞(Bump the Pointer)內(nèi)存是規(guī)整的,一邊是用過的,一邊是空閑的,中間一個(gè)指針
  • 空閑列表(Free List),記錄哪些內(nèi)存可用,查找到一個(gè)足夠大的可用的內(nèi)存空間劃分給對(duì)象,并更新列表上的記錄
    具體的策略取決于GC算法,具有整理功能(Compact)使用碰撞指針,否則使用空閑列表,默認(rèn)在新生代使用-XX:+UseTLAB
    并發(fā)分配內(nèi)存時(shí),對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理(基于CAS,Compare and Swap),或者采用本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)策略,每個(gè)線程在Java堆中預(yù)先分配一塊內(nèi)存,在該線程的內(nèi)存塊內(nèi)進(jìn)一步分配,只有TLAB用完并分配新的TLAB時(shí)才需要同步鎖定
  1. 內(nèi)存空間初始化為零值
  2. 對(duì)象頭(Object Header)信息設(shè)置
  3. 執(zhí)行init方法,即構(gòu)造方法

對(duì)象的內(nèi)存布局

對(duì)象頭(Header)

HotSpot虛擬機(jī)的對(duì)象頭(Object Header)包括兩部分信息

  • 存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù), 如哈希碼(identity_hashcode,對(duì)象的標(biāo)識(shí))、GC分代年齡、偏向鎖標(biāo)志、鎖狀態(tài)標(biāo)志、偏向線程ID、偏向時(shí)間戳、指向棧中鎖記錄的指針、指向互斥量(重量級(jí)鎖)的指針等,這部分?jǐn)?shù)據(jù)的長度在32位和64位的虛擬機(jī)中分別為32個(gè)和64個(gè)Bits,官方稱它為“Mark Word”,hotspot內(nèi)部對(duì)應(yīng)markOop.hpp,其中oop全拼是ordinary object pointer,表示指向一個(gè)對(duì)象的指針,這里的mark不是一個(gè)指針,而是一個(gè)word
  • 類型指針(指向方法區(qū)的對(duì)象類型數(shù)據(jù)),用于判定該對(duì)象是哪個(gè)類的實(shí)例

如果是數(shù)組類型,還會(huì)有4bytes(32bits)用于記錄數(shù)組的長度

64位JVM,未進(jìn)行指針壓縮的對(duì)象頭結(jié)構(gòu):

|------------------------------------------------------------------------------------------------------------|--------------------|
|                                            Object Header (128 bits)                                        |        State       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                  Mark Word (64 bits)                         |    Klass Word (64 bits)     |                    |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Normal       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Biased       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 |    OOP to metadata object   | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 |    OOP to metadata object   | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                                                     | lock:2 |    OOP to metadata object   |    Marked for GC   |
|------------------------------------------------------------------------------|-----------------------------|--------------------|

32位

//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

實(shí)例數(shù)據(jù)(Instance Data)

存儲(chǔ)父類子類的數(shù)據(jù),存儲(chǔ)順序與虛擬機(jī)字段分配策略和源碼中的定義順序有關(guān),相同寬度的字段會(huì)被分配到一起,HotSpot默認(rèn)策略是從長到短排列,引用排最后: long/double --> int/float --> short/char --> byte/boolean --> Reference

對(duì)齊填充(Padding)

占位符,默認(rèn)情況下HotSpot VM對(duì)象的起始地址是8字節(jié)的整數(shù)倍

查看內(nèi)存布局

可以使用openJDK中的JOL (Java Object Layout) 查看內(nèi)存布局

$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

$ java -jar jol-cli-0.9-full.jar  internals java.lang.Object
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

Instantiated the sample instance via default constructor.

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

可以看到,mark work為前兩行,占8字節(jié),kclass pointer占4字節(jié)(這里為64位虛擬機(jī),使用了指針壓縮),對(duì)齊消耗4字節(jié)

指針壓縮

對(duì)應(yīng)的JVM選項(xiàng)是-XX:+UseCompressedOops,默認(rèn)開啟,使用壓縮指針時(shí),對(duì)象引用實(shí)際表示的是32位偏移而不是64位指針,可以提高性能(通常64位JVM消耗的內(nèi)存會(huì)比32位的大1.5倍)

壓縮oops表示指向64位Java堆基地址的32位對(duì)象偏移量。因?yàn)樗鼈兪菍?duì)象偏移而不是字節(jié)偏移,所以它們可以用于處理4G個(gè)對(duì)象(不是字節(jié)),HotSpot VM默認(rèn)是進(jìn)行8字節(jié)對(duì)齊,地址低3位始終為0,此時(shí)可以處理32GB大小的堆, 使用過程中,JVM將它們放大8倍并加上Java堆基址以查找它們引用的對(duì)象。

當(dāng)Java堆大小大于32G時(shí),也可以使用壓縮指針,通過-XX:ObjectAlignmentInBytes=alignment選項(xiàng)設(shè)定對(duì)齊字節(jié)數(shù)目,必須是2的冪次,范圍[8,256],默認(rèn)為8,可以管理的堆空間大小為:

4GB * ObjectAlignmentInBytes

開啟指針壓縮時(shí),堆中的以下oop會(huì)被壓縮:

  • 每個(gè)對(duì)象的klass字段
  • 每個(gè)oop實(shí)例字段
  • oop數(shù)組的每個(gè)元素(objArray)

對(duì)象的訪問定位

通過棧上的reference數(shù)據(jù)指向堆上的具體數(shù)據(jù),有兩種實(shí)現(xiàn)方式:

  • 直接指針訪問:reference中存儲(chǔ)的直接就是對(duì)象地址,訪問速度更快,HotSpot使用該方式,它需要虛擬機(jī)明確知道某個(gè)位置是什么類型的數(shù)據(jù),這樣才能在對(duì)象移動(dòng)后改變Reference類型的內(nèi)容,HotSpot的JIT編譯器會(huì)生成OopMap符號(hào)信息來記錄棧上和寄存器上的引用對(duì)象位置,用來在安全點(diǎn)進(jìn)行GC
  • 句柄訪問:reference中存儲(chǔ)的是穩(wěn)定的句柄地址,對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,reference本身不需要修改

鎖狀態(tài)

根據(jù)Java對(duì)象頭,鎖一共有四種狀態(tài),無鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),這幾個(gè)狀態(tài)會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí),但不能降級(jí)


偏向鎖

大多數(shù)情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,所以當(dāng)一個(gè)線程訪問一個(gè)同步塊并獲取鎖時(shí),會(huì)通過CAS操作在對(duì)象頭里存儲(chǔ)偏向的線程ID,后續(xù)使用時(shí)只需要測(cè)試對(duì)象頭的threadID,當(dāng)其他線程試圖獲得偏向鎖CAS失敗時(shí),持有偏向鎖的線程會(huì)先暫停,恢復(fù)為無鎖狀態(tài),或者轉(zhuǎn)換為輕量級(jí)鎖

這里threadID 會(huì)覆蓋MarkWord中原有的identity hashcode,如果一個(gè)已經(jīng)偏向的對(duì)象調(diào)用object.identityHashCode()將會(huì)觸發(fā)偏向鎖的撤銷

epoch字段占2位,起到時(shí)間戳的作用

輕量級(jí)鎖

輕量級(jí)是相對(duì)使用操作系統(tǒng)互斥量來實(shí)現(xiàn)的重量級(jí)鎖而言的,在執(zhí)行同步代碼塊之前,JVM會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建一個(gè)鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的MarkWord拷貝,該拷貝叫做Displaced Mark Word

然后使用CAS操作將對(duì)象頭中的MarkWord替換為指向鎖記錄的指針,如果失敗了,表示其他線程已經(jīng)獲得了鎖,當(dāng)前線程通過自旋等待,如果自旋失敗,鎖膨脹為重量級(jí)鎖,修改Mark Word,當(dāng)前線程阻塞,等待持有鎖的線程釋放鎖并喚醒該線程

重量級(jí)鎖

鎖的最終形態(tài),標(biāo)識(shí)位為10,其中指針指向的是monitor對(duì)象(也稱為管程監(jiān)視器鎖)的起始地址。每個(gè)對(duì)象都存在著一個(gè) monitor 與之關(guān)聯(lián),對(duì)象與其 monitor 之間的關(guān)系有存在多種實(shí)現(xiàn)方式,如monitor可以與對(duì)象一起創(chuàng)建銷毀或當(dāng)線程試圖獲取對(duì)象鎖時(shí)自動(dòng)生成,當(dāng)一個(gè) monitor 被某個(gè)線程持有后,它便處于鎖定狀態(tài)。在JVM中,monitor是由ObjectMonitor實(shí)現(xiàn)的,其主要功能如下

優(yōu)化

  • 自旋鎖:當(dāng)線程申請(qǐng)鎖時(shí),鎖被占用,則讓當(dāng)前線程執(zhí)行一個(gè)忙循環(huán)(自旋),看看持有鎖的線程是否會(huì)很快釋放鎖。如果自旋后還沒獲得鎖,才進(jìn)入同步阻塞狀態(tài);
  • 自適應(yīng)自旋:時(shí)間根據(jù)之前的情況進(jìn)行調(diào)整
  • 鎖消除:根據(jù)逃逸分析,消除鎖
  • 鎖膨脹: 如果虛擬機(jī)探測(cè)到一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖,將會(huì)把加鎖同步的范圍擴(kuò)展(膨脹)到整個(gè)操作序列的外部
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JVM內(nèi)存模型Java虛擬機(jī)(Java Virtual Machine=JVM)的內(nèi)存空間分為五個(gè)部分,分別是: ...
    光劍書架上的書閱讀 2,775評(píng)論 2 26
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,816評(píng)論 11 349
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,899評(píng)論 0 11
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 16,056評(píng)論 3 83
  • 其實(shí)寫詩這事兒,是跟總在藍(lán)酒吧喝酒的阿恒和小吹學(xué)的,阿恒其實(shí)比我大三十歲,兩年前老伴走了,他戴著棕色的近視眼鏡,略...
    關(guān)馨仁閱讀 277評(píng)論 0 0

友情鏈接更多精彩內(nèi)容