Java虛擬機(jī)內(nèi)存模型
計(jì)劃發(fā)布3篇博客, 這是第一篇:jvm內(nèi)存模型
- jvm內(nèi)存模型
- 對(duì)象創(chuàng)建和內(nèi)存分配
- OOM異常
問題
java虛擬機(jī)管理內(nèi)存,無需由程序員進(jìn)行內(nèi)存的分配和釋放。
但是如果出現(xiàn)內(nèi)存泄漏或內(nèi)存溢出急膀,如何去排查問題沮协?
這就需要我們?nèi)チ私鈐ava虛擬機(jī)內(nèi)存模型
虛擬機(jī)內(nèi)存的各個(gè)區(qū)域
了解內(nèi)存模型的了解虛擬機(jī)內(nèi)存的各個(gè)區(qū)域
要點(diǎn)
- 各個(gè)區(qū)域的概念
- 各個(gè)區(qū)域的作用
- 各個(gè)區(qū)域的服務(wù)對(duì)象
- 各個(gè)區(qū)域可能會(huì)出現(xiàn)的問題
- 生命周期
運(yùn)行時(shí)數(shù)據(jù)區(qū)域
- 程序計(jì)數(shù)器(線程私有)
- java虛擬機(jī)棧(線程私有)
- 本地方法棧(線程私有)
- java堆(線程共享)
- 方法區(qū)(線程共享)
運(yùn)行時(shí)常量池:方法區(qū)一部分
直接內(nèi)存:不屬于jvm內(nèi)存模型,但也被頻繁使用卓嫂,可能導(dǎo)致內(nèi)存溢出異常
1. 程序計(jì)數(shù)器
程序計(jì)數(shù)器是什么
java虛擬機(jī)中較小的一塊內(nèi)存空間慷暂,是當(dāng)前線程所執(zhí)行的行號(hào)指示器。
每個(gè)線程都有自己的程序計(jì)數(shù)器晨雳,存儲(chǔ)程序下一條指令的指針行瑞。
程序計(jì)數(shù)器的作用是什么
字節(jié)碼解釋器通過改變程序計(jì)數(shù)器中的值,來控制程序中的循環(huán)餐禁,跳轉(zhuǎn)蘑辑,異常處理和線程的中斷和恢復(fù)。
程序計(jì)數(shù)器會(huì)發(fā)生內(nèi)存溢出嗎
程序計(jì)數(shù)器是java運(yùn)行數(shù)據(jù)區(qū)中唯一不會(huì)發(fā)生內(nèi)存溢出的區(qū)域
原因:
- 程序計(jì)數(shù)器存放的是每個(gè)線程程序的下一條指令指針坠宴,消耗內(nèi)存小洋魂。
- 在程序運(yùn)行時(shí),只需改變程序計(jì)數(shù)器中指針喜鼓,所以不需要額外申請(qǐng)內(nèi)存空間副砍。
程序計(jì)數(shù)器是線程私有的還是共有的
程序計(jì)數(shù)器是線程私有的內(nèi)存
原因:
在多線程程序執(zhí)行時(shí),一個(gè)線程的程序計(jì)數(shù)器不可能被其他線程更改庄岖,否則當(dāng)線程中斷再恢復(fù)時(shí)無法準(zhǔn)確的執(zhí)行下一條命令豁翎。
線程私有,即生命周期與線程一致
注意
如果程序正在執(zhí)行的是java方法隅忿,程序計(jì)數(shù)器記錄的是:正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址心剥;
如果程序正在執(zhí)行的是native方法,程序計(jì)數(shù)器的值為空:undefined
native方法
:本地方法背桐,由其他語言實(shí)現(xiàn)的可以在本機(jī)器執(zhí)行的方法优烧。
2. java虛擬機(jī)棧
java虛擬機(jī)棧是什么
java虛擬機(jī)棧是java方法執(zhí)行的內(nèi)存模型,每個(gè)方法執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀链峭,棧幀存儲(chǔ)局部變量表畦娄、操作數(shù)棧、動(dòng)態(tài)鏈接弊仪、方法出口等信息熙卡。每個(gè)方法從調(diào)用到執(zhí)行完成的過程,都對(duì)應(yīng)著一個(gè)棧幀在java虛擬機(jī)棧從入棧到出棧的過程励饵。
可以這樣理解:
java虛擬機(jī)棧是每個(gè)線程開始時(shí)開辟的一個(gè)棧內(nèi)存空間驳癌,用于存儲(chǔ)正在執(zhí)行的方法信息
方法調(diào)用:
- 創(chuàng)建方法棧幀
- 棧幀入java虛擬機(jī)棧
- 方法執(zhí)行
- 方法返回
- 棧幀出java虛擬機(jī)棧
java虛擬機(jī)棧和我們通常理解的java棧內(nèi)存有什么關(guān)聯(lián)
一般把java內(nèi)存分為堆內(nèi)存和棧內(nèi)存,前者是存放java對(duì)象實(shí)例役听,后者存放基本數(shù)據(jù)類型信息颓鲜。
實(shí)際上java棧內(nèi)存指的是java虛擬機(jī)棧中表窘,每個(gè)方法棧幀中的局部變量表部分。
什么是局部變量表
局部變量表存放的是編譯期可知的各種基本數(shù)據(jù)類型和對(duì)象引用類型灾杰。
局部變量表存放各種基本類型和對(duì)象引用類型能理解蚊丐,就和我們常說的java棧內(nèi)存一樣熙参,什么是編譯器可知艳吠?
java類在編譯期時(shí),一個(gè)方法中有多少基本數(shù)據(jù)類型和對(duì)象引用類型是可知的孽椰,而這些基本數(shù)據(jù)類型和對(duì)象引用類型的所占空間也是已知的昭娩,除了64位長度的long和double類型占用兩個(gè)局部變量空間,其他的都只占用一個(gè)局部變量空間黍匾。
所以局部變量表在編譯期即可確定需要分配多少內(nèi)存空間栏渺。在運(yùn)行期間,進(jìn)入一個(gè)方法時(shí)锐涯,直接分配指定大小的內(nèi)存空間磕诊,而不會(huì)改變局部變量表的大小。
即局部變量表在編譯期可知纹腌,在運(yùn)行期直接分配霎终。
棧幀中除了局部變量表還有其他類型吧
棧幀是一種特殊的數(shù)據(jù)結(jié)構(gòu),存儲(chǔ):
- 局部變量表
- 操作數(shù)棧
- 動(dòng)態(tài)鏈接
- 方法出口
java虛擬機(jī)棧會(huì)出現(xiàn)內(nèi)存溢出嗎
會(huì)升薯,而且分兩種情況:
- java虛擬機(jī)棧大小不可動(dòng)態(tài)擴(kuò)展莱褒,當(dāng)前線程請(qǐng)求的棧深度 > 虛擬機(jī)所允許的深度, 拋出內(nèi)存溢出異常涎劈。
- java虛擬機(jī)棧大小可動(dòng)態(tài)擴(kuò)展广凸,擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存, 拋出內(nèi)存溢出異常蛛枚。
java虛擬機(jī)棧默認(rèn)可動(dòng)態(tài)擴(kuò)展谅海,可以通過參數(shù)設(shè)置。
實(shí)際案例中有因?yàn)閖ava虛擬機(jī)棧導(dǎo)致的內(nèi)存溢出嗎
最常見的就是:死遞歸時(shí)蹦浦,java虛擬機(jī)棧內(nèi)存空間耗盡胁赢,拋出內(nèi)存溢出異常。
java虛擬機(jī)棧是線程私有的白筹,即生命周期與線程一致
3. 本地方法棧
本地方法棧是什么
本地方法棧與虛擬機(jī)棧很相似智末,區(qū)別就是:
- Java虛擬機(jī)棧是為虛擬機(jī)執(zhí)行Java方法提供服務(wù)
- 本地方法棧是虛擬機(jī)執(zhí)行native方法提供服務(wù)
這么說本地方法棧也會(huì)拋出棧溢出和內(nèi)存溢出的異常?
是的徒河,本地方法棧也會(huì)拋出StackOverflowError和OutOfMemoryError異常
注意
虛擬機(jī)規(guī)范中并沒有對(duì)本地方法棧中方法所用的語言系馆、使用方式和數(shù)據(jù)結(jié)構(gòu)進(jìn)行強(qiáng)制規(guī)定,虛擬機(jī)可以自由實(shí)現(xiàn)顽照。
所以Sun HotSpot虛擬機(jī)直接把本地方法棧和虛擬機(jī)棧合而為一
4. java堆
堆我知道由蘑,就是Java存放所有對(duì)象實(shí)例的內(nèi)存空間
是的闽寡,Java堆是Java虛擬機(jī)管理的最大一塊內(nèi)存空間,被所有線程共享尼酿,在虛擬機(jī)啟用時(shí)創(chuàng)建爷狈。
幾乎所有的對(duì)象實(shí)例都在堆上存放。
為什么說幾乎
因?yàn)殡S著jit編譯器的發(fā)展和逃逸分析技術(shù)逐漸成熟裳擎,也可能對(duì)象實(shí)例使用棧上分配和標(biāo)量替換
簡單說下對(duì)象實(shí)例的棧上分配和標(biāo)量替換的優(yōu)化技術(shù)
簡單說下自己了解的涎永,Java堆上可以分配多個(gè)線程私有的線程本地分配緩沖區(qū)(TLAB)
。
對(duì)象實(shí)例由對(duì)應(yīng)的基本類型組成鹿响,使用逃逸分析判斷是否對(duì)象是否逃逸羡微,如果不逃逸,直接將對(duì)象所對(duì)應(yīng)的基本類型在TLAB上存儲(chǔ)惶我。當(dāng)線程結(jié)束后妈倔,TLAB上對(duì)象清理。
虛擬機(jī)棧是棾窆保空間盯蝴,那堆空間是什么樣的呢
堆空間在物理上可以是不連續(xù)的,只需要在邏輯上是連續(xù)的即可听怕,和我們本地磁盤一樣捧挺。
堆空間可以實(shí)現(xiàn)成固定大小,也可以動(dòng)態(tài)擴(kuò)展叉跛,主流虛擬機(jī)都動(dòng)態(tài)擴(kuò)展的松忍。
堆會(huì)發(fā)生內(nèi)存溢出嗎
會(huì)。如果堆中沒有內(nèi)存完成實(shí)例分配筷厘,而且堆無法再擴(kuò)展時(shí)鸣峭,就會(huì)拋出內(nèi)存溢出異常
堆和垃圾收集器有什么關(guān)系
堆是Java垃圾收集器管理的主要區(qū)域,因此很多時(shí)候被稱為GC堆
關(guān)于Java垃圾收集器在第三章講解
5. 方法區(qū)
什么是方法區(qū)
和Java堆一樣酥艳,是各個(gè)線程共享的內(nèi)存區(qū)域摊溶,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。
主要存儲(chǔ):
- 被虛擬機(jī)加載的類信息
- 常量
- 靜態(tài)變量
- 即時(shí)編譯器編譯后的代碼
方法區(qū)會(huì)發(fā)生內(nèi)存溢出嗎
會(huì)充石。如果方法區(qū)無法滿足內(nèi)存分配需求時(shí)莫换,就會(huì)拋出內(nèi)存溢出異常。
而且骤铃,在低版本的虛擬機(jī)中拉岁,方法區(qū)發(fā)生內(nèi)存溢出的問題更常見。
這時(shí)為什么呢
Java在1.8版本之前惰爬,使用了堆中永生代來實(shí)現(xiàn)了方法區(qū)喊暖。
優(yōu)勢:Java虛擬機(jī)可以像管理堆那樣管理方法區(qū),省去了專門為方法區(qū)編寫內(nèi)存管理代碼的工作撕瞧。
劣勢:這樣更容易發(fā)生內(nèi)存溢出異常陵叽。因?yàn)橛郎幸粋€(gè)內(nèi)存上限狞尔,加上整個(gè)方法區(qū)占用空間,內(nèi)存溢出風(fēng)險(xiǎn)大大提高巩掺。
java在1.8版本中完全移除永久代的實(shí)現(xiàn)偏序,采用了metaspace(元空間)代替。元空間不在虛擬機(jī)中胖替,而是使用本地內(nèi)存研儒。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池是什么
運(yùn)行時(shí)常量池是方法區(qū)的一部分。
方法區(qū)存儲(chǔ)被虛擬機(jī)加載的類信息刊殉,而Class文件中有:
- 類的版本號(hào)
- 字段
- 方法
- 接口
- 常量池
常量池存放:編譯期生成的各種字面量和符號(hào)引用
而常量池在運(yùn)行時(shí)會(huì)被放到運(yùn)行時(shí)常量池中殉摔。
什么是字面量和符號(hào)引用
字面量:String a = "hello"; 字符串"hello" 就是字面量
符號(hào)引用:存在于class文件中州胳,運(yùn)行時(shí)經(jīng)過動(dòng)態(tài)鏈接變成對(duì)象的直接引用记焊。因?yàn)閖ava的多態(tài)性,在運(yùn)行時(shí)確定具體的引用對(duì)象栓撞,所以編譯期的是符號(hào)引用遍膜,被虛擬機(jī)嚴(yán)格規(guī)定的對(duì)象引用字面量。
運(yùn)行時(shí)常量池會(huì)發(fā)生內(nèi)存溢出嗎
運(yùn)行時(shí)常量池是方法區(qū)的一部分瓤湘,所以和方法區(qū)一致瓢颅,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí),拋出內(nèi)存溢出異常
常量池弛说、運(yùn)行時(shí)常量池挽懦、字符串常量池這三個(gè)都弄糊涂了
- 常量池:即class文件常量池,是class文件的一部分木人,用于保存編譯時(shí)確定的數(shù)據(jù)信柿。
- 保存的內(nèi)容:
-
- 字面量
- 文本字符串
- 被聲明為final的常量值
- 基本數(shù)據(jù)類型的值
- 其他
-
- 符號(hào)引用
- 類和結(jié)構(gòu)的完全限定名
- 字段名稱和描述符
- 方法名稱和描述符
-
-
運(yùn)行時(shí)常量池:
- java不一定在編譯期產(chǎn)生,運(yùn)行期也可能產(chǎn)生常量醒第,例如使用String.intern()方法渔嚷。這些常量被放到運(yùn)行時(shí)常量池中。
- 類加載后稠曼,常量池中數(shù)據(jù)也會(huì)在運(yùn)行時(shí)常量池中存放
字符串常量池:在JDK1.7之前的HotSpot JVM中形病,記錄interned String的一個(gè)全局表 StringTable, 本質(zhì)是HashSet<String>。他只存儲(chǔ)Java.lang.String的引用霞幅,而不記錄String的內(nèi)容漠吻。
- 全局共享,只有一份司恳。1.8之后從永生代移到j(luò)ava堆中
java1.8方法區(qū)變化
- 移除了永生代途乃,使用元空間實(shí)現(xiàn)方法區(qū)。
- 永久代的class metadata抵赢,即被虛擬機(jī)加載的類信息欺劳,移到元空間
- 永久代的字符串常量池和靜態(tài)變量唧取,移到j(luò)ava堆中
- 永久代參數(shù) -> 元空間參數(shù)
關(guān)于java1.8的內(nèi)存模型,參考:
直接內(nèi)存
直接內(nèi)存是什么划提,不在java虛擬機(jī)五個(gè)數(shù)據(jù)區(qū)域里呀
是的枫弟,直接內(nèi)存并不屬于java虛擬機(jī)管理的五個(gè)數(shù)據(jù)區(qū)域。但也是經(jīng)常用到的鹏往,也可能會(huì)發(fā)生內(nèi)存溢出異常淡诗。
在jdk1.4中引入了NIO類,使用基于通道和緩沖區(qū)的I/O方式伊履。
它是使用Native函數(shù)庫直接分配堆外內(nèi)存韩容,然后通過存儲(chǔ)在堆中的DirectByteBuffer對(duì)象,作為這塊內(nèi)存的引用進(jìn)行操作唐瀑。
這樣在一些場景中可以顯著的提高性能群凶,因?yàn)楸苊饬嗽贘ava堆和Native堆中來回復(fù)制數(shù)據(jù)。
既然直接內(nèi)存是堆外內(nèi)存哄辣,不受java堆大小的限制请梢,為什么還會(huì)發(fā)生內(nèi)存溢出呢?
因?yàn)樗€是內(nèi)存空間力穗,會(huì)受到本機(jī)系統(tǒng)的內(nèi)存大小限制毅弧。
如果虛擬機(jī)各個(gè)內(nèi)存區(qū)域總和 > 物理內(nèi)存限制,就很可能導(dǎo)致直接內(nèi)存動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn)內(nèi)存溢出異常当窗。
總結(jié)
java虛擬機(jī)內(nèi)存模型
1. java虛擬機(jī)5個(gè)內(nèi)存區(qū)域
- 程序計(jì)數(shù)器
- 生命周期:線程私有够坐,隨線程存在
- 存儲(chǔ)內(nèi)容:存儲(chǔ)當(dāng)前線程下一條指令的引用
- 內(nèi)存溢出異常:沒有內(nèi)存溢出異常
- java虛擬機(jī)棧
- 生命周期:線程私有,隨線程存在
- 存儲(chǔ)內(nèi)容:存儲(chǔ)當(dāng)前線程正在執(zhí)行方法的棧幀
- 內(nèi)存溢出異常:
- 虛擬機(jī)棧不可擴(kuò)展崖面,棧請(qǐng)求的深度 > 虛擬機(jī)允許的棧深度
- 虛擬機(jī)椩可擴(kuò)展,擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存
- 本地方法棧
- 生命周期:線程私有嘶朱,隨線程存在
- 存儲(chǔ)內(nèi)容:存儲(chǔ)當(dāng)前線程正在執(zhí)行的本地方法的棧幀
- 內(nèi)存溢出異常:
- 本地方法棧不可擴(kuò)展蛾坯,棧請(qǐng)求的深度 > 虛擬機(jī)允許的棧深度
- 本地方法棧可擴(kuò)展疏遏,擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存
- java堆
- 生命周期:線程共享脉课,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
- 存儲(chǔ)內(nèi)容:幾乎所有的java對(duì)象實(shí)例
- 內(nèi)存溢出異常:堆中沒有內(nèi)存空間完成對(duì)象內(nèi)存分配,且擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存
- 方法區(qū)
- 生命周期:線程共享财异,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
- 存儲(chǔ)內(nèi)容:被虛擬機(jī)加載的類信息倘零、靜態(tài)變量、常量
- 內(nèi)存溢出異常:無法申請(qǐng)到足夠的內(nèi)存
java虛擬機(jī)管理之外的內(nèi)存區(qū)域
直接內(nèi)存
- 生命周期::線程共享戳寸,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
- 存儲(chǔ)內(nèi)容:Java NIO的Channel和Buffer
- 內(nèi)存區(qū)域:使用Native函數(shù)庫直接分配堆外內(nèi)存
- 內(nèi)存溢出異常:本地內(nèi)存用完
java1.8方法區(qū)變化
- 移除了永生代呈驶,使用元空間實(shí)現(xiàn)方法區(qū)。
- 永久代的class metadata疫鹊,即被虛擬機(jī)加載的類信息袖瞻,移到元空間
- 永久代的字符串常量池和靜態(tài)變量司致,移到j(luò)ava堆中
- 永久代參數(shù) -> 元空間參數(shù)
想共同學(xué)習(xí)jvm的可以加我微信:1832162841,或者進(jìn)QQ群:982523529