jvm內(nèi)存模型及內(nèi)存溢出簡介

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

Java虛擬機(jī)在運(yùn)行Java程序時(shí)會(huì)將他管理的內(nèi)存分為若干個(gè)不同的數(shù)據(jù)區(qū)域,有的在Java虛擬機(jī)進(jìn)程中一直存在有的依賴線程的啟動(dòng)和介紹而建立和銷毀。包括:程序計(jì)數(shù)器,Java虛擬機(jī)棧,本地方法棧,Java堆,方法區(qū),運(yùn)行時(shí)常量池,直接內(nèi)存


1.1程序計(jì)數(shù)器

可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)的指示器;

每條線程都有自己獨(dú)立的程序計(jì)數(shù)器,用來記錄當(dāng)前線程正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址(當(dāng)前操作的是Java方法,如果是本地方法計(jì)數(shù)器的值為空)

原因:多線程的情況下,存在頻繁的上下文切換(一個(gè)處理器同一時(shí)刻只能處理一個(gè)線程),所以需要記錄當(dāng)前線程執(zhí)行的位置

各線程之間的計(jì)數(shù)器互不影響,存在于線程的“私有內(nèi)存”中

1.2Java虛擬機(jī)棧

1.線程私有

2.虛擬機(jī)棧描述的是Java執(zhí)行過程中線程的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候,Java虛擬機(jī)都會(huì)同步創(chuàng)建一個(gè)棧幀用于存儲(chǔ) 局部變量,操作數(shù)棧,動(dòng)態(tài)連接,方法出口等信息。每個(gè)方法被調(diào)用直至執(zhí)行完畢的過程,就對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧(數(shù)據(jù)結(jié)構(gòu) 因?yàn)榉椒ㄊ菍蛹?jí)調(diào)用)的過程。

2.存放基本數(shù)據(jù)類型和對(duì)象引用,對(duì)象實(shí)例存放在堆中

如何理解棧和棧幀的區(qū)別?

Java虛擬機(jī)啟動(dòng)的時(shí)候會(huì)指定棧和堆的大小 好比棧的大小是1024k

每個(gè)線程都能1024K大小的??臻g

線程每進(jìn)一個(gè)方法會(huì)創(chuàng)建一個(gè)棧幀,好比進(jìn)入了很多方法創(chuàng)建的棧幀超過了棧的大小就會(huì)棧溢出

在可以接受的范圍內(nèi),棧的大小設(shè)置的越小方法返回的速度越快

如何確定java方法棧(棧幀)的大???

其他的暫時(shí)還不知道,先說一下局部變量

局部變量表存放編譯期可知的基本數(shù)據(jù)類型,引用數(shù)據(jù)類型,returnAddress(指向了一條字節(jié)碼指令的地址)

局部變量表所需的空間在編譯期就完成了分配,運(yùn)行時(shí)當(dāng)進(jìn)入一個(gè)方法時(shí)需要在棧幀中分配多大的局部變量空間是確定的

HotSpot 虛擬機(jī)棧幀 的大小是不能動(dòng)態(tài)擴(kuò)展的

猜想創(chuàng)建棧幀大小時(shí)應(yīng)該是有個(gè)讀取的依據(jù)

設(shè)置堆棧大小? ? ? 棧溢出這個(gè)棧的大小是什么時(shí)候規(guī)定的

對(duì)于Java方法棧這塊內(nèi)存區(qū)域可能出現(xiàn)的異常:

StackOverflowError棧溢出,如果線程請(qǐng)求的棧的深度大于虛擬機(jī)允許的深度就會(huì)拋出這個(gè)異常

OutOfMemoryError堆溢出,如果虛擬機(jī)棧大小可以動(dòng)態(tài)擴(kuò)展(有的虛擬機(jī)可以像classic),當(dāng)無法申請(qǐng)到足夠的內(nèi)存就會(huì)拋這個(gè)異常

可能出現(xiàn)棧溢出的場(chǎng)景:方法的循環(huán)調(diào)用(循環(huán)中一直在調(diào)用就一直創(chuàng)建棧幀,循環(huán)中所有的方法一直沒有釋放就一直不會(huì)出棧,所以很容易就棧溢出了)

1.3本地方法棧

和Java方法棧和相似,只不過一個(gè)執(zhí)行的是Java方法服務(wù)一個(gè)是本地方法服務(wù)

和虛擬機(jī)棧一樣,本地方法棧也會(huì)在深度溢出和擴(kuò)展失敗是拋出StackOverflowError和OutOfMemoryError

hotspot直接將這兩個(gè)方法棧合二為一了

1.4java堆

1存儲(chǔ)對(duì)象

幾乎所有的對(duì)象實(shí)例都在堆內(nèi)存當(dāng)中,堆內(nèi)存區(qū)域所有線程共享

堆中具體存放那些對(duì)象?

全局普通變量是否存在堆中

2細(xì)化

垃圾回收的對(duì)象也是堆

現(xiàn)代垃圾回收器大部分是基于分代收集理論設(shè)計(jì),所以堆又被劃分為新生代、老年代、永久代

這種區(qū)域劃分并非堆的固有設(shè)計(jì)而是垃圾回收器的特性

也有不采用分代回收的垃圾回收器

3其他特性

可以在物理上處于不連續(xù)的內(nèi)存空間

堆的大小上可以設(shè)置成固定的也可以設(shè)置成可擴(kuò)展的 -Xmx -Xms (可擴(kuò)展的當(dāng)無法擴(kuò)展時(shí)就會(huì)內(nèi)存溢出)

1.5方法區(qū)(別名 非堆)

1.線程共享

2.存放已被虛擬機(jī)加載的類信息(類名,訪問修飾符、字段描述、方法描述)、常量、靜態(tài)變量、即時(shí)編譯期編譯后的代碼緩存等數(shù)據(jù)

3.方法區(qū)的垃圾回收

常量池的回收和類的卸載

Hotspot中方法區(qū)和“永久代”的關(guān)系及演變

最早是使用永久代來實(shí)現(xiàn)方法區(qū),相當(dāng)于永久代就是方法區(qū)

方法區(qū)物理上存在堆里,而且是在堆的持久代里面;但在邏輯上,方法區(qū)和堆是獨(dú)立的。

一般說堆的持久代就是說方法區(qū),因?yàn)橐坏㎎VM把方法區(qū)(類信息,常量,靜態(tài)靜態(tài)變量)加載進(jìn)內(nèi)存以后,這些內(nèi)存一般是不會(huì)被回收的了。

壞處:這種方式更容易內(nèi)存溢出,永久代有內(nèi)存大小限制,其他虛擬機(jī)方法區(qū)可以使用進(jìn)程最大內(nèi)存

Hotspot中的演變:jdk7 將永久代中的字符串常量池,靜態(tài)變量移出到堆中

jdk8廢棄了永久代的概念,將剩下的類信息等全部放到元空間

1.6運(yùn)行時(shí)常量池

1.是方法區(qū)的一部分,存放class文件的常量池表里的數(shù)據(jù)和運(yùn)行時(shí)生成的常量數(shù)據(jù)

1.7直接內(nèi)存

不是虛擬機(jī)運(yùn)行時(shí)內(nèi)存區(qū)域,其內(nèi)存大小的設(shè)置會(huì)影響到j(luò)vm內(nèi)存

如果 其他內(nèi)存 + jvm內(nèi)存 > 總內(nèi)存? 容易觸發(fā)jvm內(nèi)存溢出

直接內(nèi)存也可以通過參數(shù)設(shè)置,如果不設(shè)置默認(rèn)和堆大小一致

1.8普通Java對(duì)象的創(chuàng)建過程

1.8.1創(chuàng)建過程

1.根據(jù)指令參數(shù)在常量池中看能否找到對(duì)應(yīng)的類的符號(hào)引用

2.根據(jù)符號(hào)引用看這個(gè)類是否已經(jīng)被加載、解析、初始化了

3.沒有,則執(zhí)行類的初始化過程

4.在堆中為新對(duì)象分配內(nèi)存空間(“指針碰撞”、”空閑列表“)(對(duì)象的內(nèi)存大小在類加載完就確定了)5.設(shè)置對(duì)象頭信息(偏向鎖信息,對(duì)象是哪個(gè)類的實(shí)例,GC分代年齡)

6.執(zhí)行Class的<init>方法,對(duì)對(duì)象進(jìn)行初始化

7.對(duì)象引用入棧


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

對(duì)象頭:

對(duì)象運(yùn)行時(shí)數(shù)據(jù): 哈希嗎、GC年齡分代、線程持有的鎖、偏向鎖的線程ID、偏向鎖時(shí)間戳

類型指針:指向類的元數(shù)據(jù)的指針,元數(shù)據(jù)在方法區(qū)中(Hotspot里的在元空間)

如果對(duì)象是數(shù)組還有塊空間記錄數(shù)組大小

實(shí)例數(shù)據(jù):

記錄父類、子類中各種定義的類型的字段內(nèi)容

對(duì)齊填充:

虛擬機(jī)要求對(duì)象的大小必須是8字節(jié)的整數(shù)倍,如果對(duì)象不是需要對(duì)齊填充來補(bǔ)充,沒有實(shí)際意義

為什么要是8字節(jié)的倍數(shù)?

棧中存放的對(duì)象引用是什么?

對(duì)象在堆中的地址值

or對(duì)象在句柄池中的地址,句柄存放的是對(duì)象實(shí)例數(shù)據(jù)指針和對(duì)象元數(shù)據(jù)指針

二、內(nèi)存溢出

除了程序計(jì)數(shù)器之外其他的內(nèi)存區(qū)域都有可能內(nèi)存溢出

內(nèi)存泄漏和內(nèi)存溢出的區(qū)別

2.1堆溢出

2.1.1堆溢出的原因

堆溢出是由于沒被引用的對(duì)象(垃圾)過多造成JVM沒有及時(shí)回收,造成堆的溢出。如果出現(xiàn)這種現(xiàn)象可行代碼排查:

一)是否App中的類中和引用變量過多使用了Static修飾 如public staitc Student s;在類中的屬性中使用 static修飾的最好只用基本類型或字符串。如public static int i = 0; //public static String str;

二)是否App中使用了大量的遞歸或無限遞歸(遞歸中用到了大量的建新的對(duì)象)

三)是否App中使用了大量循環(huán)或死循環(huán)(循環(huán)中用到了大量的新建的對(duì)象)

四)檢查App中是否使用了向數(shù)據(jù)庫查詢所有記錄的方法。即一次性全部查詢的方法,如果數(shù)據(jù)量超過10萬多條了,就可能會(huì)造成內(nèi)存溢出。所以在查詢時(shí)應(yīng)采用“分頁查詢”。

五)檢查是否有數(shù)組,List,Map中存放的是對(duì)象的引用而不是對(duì)象,因?yàn)檫@些引用會(huì)讓對(duì)應(yīng)的對(duì)象不能被釋放。會(huì)大量存儲(chǔ)在內(nèi)存中。

六)檢查是否使用了“非字面量字符串進(jìn)行+”的操作。因?yàn)镾tring類的內(nèi)容是不可變的,每次運(yùn)行"+"就會(huì)產(chǎn)生新的對(duì)象,如果過多會(huì)造成新String對(duì)象過多,從而導(dǎo)致JVM沒有及時(shí)回收而出現(xiàn)內(nèi)存溢出。

2.2.2堆溢出的排查方式

1.設(shè)置參數(shù)-XX:+HeapDumpOnOutOfMemoryError 堆溢出前將堆轉(zhuǎn)儲(chǔ)快照

2.根據(jù)快照文件排查是內(nèi)存泄漏還是內(nèi)存溢出

3.如果是內(nèi)存泄漏找到內(nèi)存泄漏的代碼

4.如果是內(nèi)存溢出,根據(jù)快照文件找到內(nèi)存溢出代碼是否有生命周期過長,持有時(shí)間過長,存儲(chǔ)結(jié)構(gòu)不合理等情況,同時(shí)優(yōu)化堆的配置

2.2棧溢出

2.2.1棧溢出原因

1.棧的內(nèi)存過小,虛擬機(jī)請(qǐng)求的棧的深度超過了所允許的最大深度

2.命名了大量對(duì)象,超過了本地變量表長度

2.3方法區(qū)溢出

2.3.1方法區(qū)溢出原因

1.程序中有生成大量動(dòng)態(tài)類的場(chǎng)景

2.有大量jsp或動(dòng)態(tài)生成jsp的應(yīng)用(jsp第一次運(yùn)行需要編譯為Java類)

3.存在OSGI的應(yīng)用(不同的方式加載生成不同的類)

總之,運(yùn)行時(shí)生成了大量類的信息,導(dǎo)致溢出

2.4本機(jī)直接內(nèi)存溢出

本機(jī)直接內(nèi)存如不設(shè)置默認(rèn)和堆大小一致,NIO的操作容易導(dǎo)致直接內(nèi)存溢出

?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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