簡述JVM內(nèi)存模型

我們都知道,計(jì)算機(jī)中所有程序都是再內(nèi)存中運(yùn)行的,在程序運(yùn)行的過程中,需要不斷的將內(nèi)存的邏輯地址和物理地址進(jìn)行映射,找到相應(yīng)的命令和數(shù)據(jù)。作為操作系統(tǒng)進(jìn)程,Java也會(huì)面臨內(nèi)存限制。即內(nèi)存架構(gòu)所提供的可尋址地址空間,在32位處理器中,這個(gè)可尋址空間位2^32可尋址范圍,64可尋址空間為2^64。在操作系統(tǒng)中,定義了兩塊空間,即內(nèi)核空間和用戶空間,Java運(yùn)行在用戶空間上。

JVM內(nèi)存模型

在JVM中,可以從線程私有和共享來劃分它的內(nèi)存模型
線程私有的:程序計(jì)數(shù)器,虛擬機(jī)棧,本地方法棧
共享的:方法區(qū),Java堆

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

當(dāng)前代碼的字節(jié)碼的指示器,它可以選取下一條需要執(zhí)行的字節(jié)碼指令,進(jìn)程序的流程控制,包括循環(huán),if分支等等。程序計(jì)數(shù)器在只占還很小的一塊內(nèi)存空間,它線程私有的,在確定的時(shí)刻,一個(gè)線程都會(huì)在一個(gè)處理器上執(zhí)行,為了保證線程切換后能在正確的執(zhí)行位置上,所以每個(gè)線程都需要一個(gè)程序計(jì)數(shù)器。對(duì)于程數(shù)計(jì)數(shù)器,不用擔(dān)心內(nèi)存泄露,因?yàn)樗膬?nèi)存地址是邏輯地址而非物理地址。如果執(zhí)行的是Native方法,計(jì)數(shù)器位是undefined。

Java虛擬機(jī)棧(VM Stack)

Java虛擬機(jī)棧也是線程私有的,每個(gè)方法執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀,它是方法運(yùn)行時(shí)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。棧幀中主要存儲(chǔ)以下的信息:

  • 局部變量表
  • 操作數(shù)棧
  • 動(dòng)態(tài)鏈接
  • 地址返回

當(dāng)方法調(diào)用結(jié)束的時(shí)候,棧幀會(huì)被銷毀。
在局部變量表的中,存儲(chǔ)了編譯期可知的各種Java虛擬機(jī)的基本數(shù)據(jù)類型,對(duì)象引用地址,字節(jié)碼指令地址等。這些類型的在局部變量表中的存儲(chǔ)以局部變量槽(slot)來表示,除了doublelong占了兩個(gè)槽,其他都是占一個(gè)。
當(dāng)遞歸調(diào)用層級(jí)太多的時(shí)候,就會(huì)發(fā)生StackOverflowError異常。限制遞歸的次數(shù),棧有固定的容量,不需要GC釋放,會(huì)自動(dòng)釋放。

虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,當(dāng)無法申請(qǐng)到足夠的內(nèi)存就會(huì)發(fā)生OOM的異常。通過設(shè)置-xss指定每個(gè)線程虛擬機(jī)棧的大小。

public void stackOOM(){
   while(true){
      new Thread(()->{
          while(true){}
      }).start();
  }
}

Window平臺(tái)的JVM映射到操作系統(tǒng)內(nèi)核上,所以運(yùn)行可能會(huì)產(chǎn)生假死的情況,請(qǐng)謹(jǐn)慎使用。

本地方法棧(Native Method Stack)

本地方法棧和虛擬機(jī)棧類似,JVM并未要求本地方法棧一定實(shí)現(xiàn)某種語言的方法調(diào)用,使用者可以靈活去進(jìn)行調(diào)用。

Java堆(Heap)

堆模型

Java堆在JVM中是一塊最大的內(nèi)存,《Java虛擬機(jī)規(guī)范》描述“所有對(duì)象和數(shù)組都應(yīng)該在堆上分配”,但是隨著即時(shí)編譯技術(shù)的發(fā)展,在Java11中,我們已經(jīng)可以看到可以使用Java不經(jīng)過編譯直接運(yùn)行了,實(shí)現(xiàn)了真正的“JavaScript”,這些逃逸分析,標(biāo)量替換的技術(shù)可以實(shí)現(xiàn)對(duì)象在棧上分配。

如上圖所示,在堆上劃分了很多區(qū)域,Java堆實(shí)際上是垃圾收集器(GC)管理的一塊內(nèi)存區(qū)域,經(jīng)過之上的劃分得以進(jìn)行更有效率的垃圾回收。在后面的文章會(huì)寫到。

另外,在堆中還定義了一些設(shè)置參數(shù)。通過Xmx設(shè)置堆最大內(nèi)存,Xms設(shè)置最小內(nèi)存,Xms和Xmx一般都設(shè)置一樣大小的,因?yàn)樵贁U(kuò)容的時(shí)候會(huì)引起內(nèi)存抖動(dòng),影響性能。

Java堆在物理上不要求連續(xù),在邏輯上連續(xù)即可。

方法區(qū)(Method Area)

方法區(qū)是線程共享的區(qū)別,在Java8以前,方法區(qū)中定義了永久代。因?yàn)槭褂糜谰么鷣韺?shí)現(xiàn)了方法區(qū),所以被描述為堆的一個(gè)邏輯部分。但是它確是“非堆”,只是設(shè)計(jì)堆中的收集器 擴(kuò)展到了方法區(qū)而已。在Java8的時(shí)候,永久代被替換成了元空間(Metaspace)。這樣做的好處有以下原因:

  • Metaspace使用的是本地內(nèi)存
  • 字符串常量池存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。
  • 類和方法的信息難以確定,給永久代指定大小比較困難,太小容易操作永久代溢出,太大會(huì)導(dǎo)致老年代溢出。
  • 永久代會(huì)為GC帶來不必要的復(fù)雜性。
  • 方便不同的JVM
    在元空間中,存儲(chǔ)了虛擬機(jī)加載的類信息,常量,靜態(tà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)容