日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

java 栈内存结构_JVM内存结构概念解析

發(fā)布時間:2025/3/20 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 栈内存结构_JVM内存结构概念解析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一. Java 內(nèi)存結(jié)構(gòu)

Java代碼運行在虛擬機上,虛擬機在運行過程將程序(也就是進程)所占有內(nèi)存分為幾個不同的數(shù)據(jù)區(qū)域。不同的區(qū)域有不同的職責(zé)。

Java運行時內(nèi)存結(jié)構(gòu)圖如下:

Java運行時內(nèi)存結(jié)構(gòu)圖

1. PC寄存器(程序計數(shù)器):

當(dāng)前線程執(zhí)行的字節(jié)碼的行號指示器。字節(jié)碼解釋器通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。

線程私有(每條線程有獨立的計數(shù)器)

沒有OOM的區(qū)域

如執(zhí)行的是Native方法,則計數(shù)器的值為空

2. 虛擬機棧:

也就是我們常說的線程方法棧。大致符合所了解的先入后出的棧結(jié)構(gòu)特性,其出入棧的數(shù)據(jù)結(jié)構(gòu)稱為棧幀,也就是調(diào)用某個方法A時,則會入棧一個棧幀,這個棧幀結(jié)構(gòu)包括了方法A中的局部變量區(qū)、操作數(shù)據(jù)棧,動態(tài)鏈接、方法的返回地址等。

編譯期間確定棧幀的大小。虛擬機棧大小也比較小,大概1M左右,故而可能發(fā)生StackOverfollowError, 比如無限遞歸調(diào)用。

局部變量區(qū):局部變量,在方法內(nèi)聲明的變量。其編譯期間就確定這個區(qū)間的大小。

操作數(shù)棧:也是先入后出,JVM的指令集在操作時都是對操作棧上的數(shù)據(jù)進行操作,比如說算術(shù)運算、方法調(diào)用時的參數(shù)傳遞。

動態(tài)鏈接:棧幀中包含一個運行時常量池中該棧幀所屬方法的引用

方法A調(diào)用方法B的調(diào)用過程大致是:方法A存有方法B的在常量池的符號引用b,然后根據(jù)指令將符號引用b作為參數(shù),并將b解析為方法B真正所在的內(nèi)存地址,

直接引用,然后進行方法B真正調(diào)用。生成的棧幀就持有了自身方法的引用。

這些符號引用一部分在類加載階段或第一次使用的時候就被轉(zhuǎn)換直接引用的,就稱為靜態(tài)解析,比如靜態(tài)方法以及私有方法。在運行期被轉(zhuǎn)換為直接引用的過程稱之為動態(tài)鏈接。

方法返回地址:方法退出時(正常return或異常退出),回到方法被調(diào)用的位置,并會返回一個值給上層方法(若有的話),并恢復(fù)上層方法的執(zhí)行狀態(tài)。

3. 本地方法棧:

當(dāng)調(diào)用原生代碼時的方法棧。作用類似于虛擬機棧。

而數(shù)據(jù)結(jié)構(gòu)、使用方式。都由虛擬機自由發(fā)揮。

4. 堆:

堆棧...我們比較熟悉也可能長掛在嘴邊的一個詞。棧我們都知道了,就是上面的虛擬機棧,也確實是有先進后出的一種特性。但這里的堆就跟數(shù)據(jù)結(jié)構(gòu)中的堆完全不同概念了,也沒有什么共性。

堆:就是一個主要用于存放對象的內(nèi)存區(qū)域,線程共享的一塊區(qū)域,堆允許程序在運行時動態(tài)地申請某個大小的內(nèi)存空間。

堆中有一個不得不提的事 --- 垃圾回收(GC)

堆劃分為兩個區(qū)域: 新生代和老年代,默認(rèn)比例1:2。新生代又劃分為Eden和 Survivor,而Survivor又劃分為from和to兩個區(qū)域。其中默認(rèn)比例為 Eden:from:to = 8:1:1, 運行時,from和to有一塊區(qū)域是會處于閑置狀態(tài),GC的時候,兩個狀態(tài)發(fā)生切換。

當(dāng)我們新建一個對象的時候,絕大多數(shù)新建的對象被放在 Eden 區(qū)域(例外的有,需要更多的一大片連續(xù)的儲存空間只有老年代才能滿足的時候)。

而這個對象不再使用的時候,系統(tǒng)則需要對其回收,以便更好分配內(nèi)存。

GC分為兩種,Minor GC 和 Full GC

GC時間不可預(yù)測,滿足觸發(fā)條件,系統(tǒng)才GC,若GC后的空間仍不滿足,則會發(fā)生OOM。GC過程會 stop the world, 根據(jù)對象的引用鏈,標(biāo)記不可達(dá)對象。可調(diào)用System.gc() 提醒系統(tǒng)觸發(fā)GC(Full GC)。

判斷對象是否存活

引用計數(shù):判斷對象被引用的次數(shù),無法解決相互引用的問題。

可達(dá)性分析:從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當(dāng)一個對象不存在于任何一條GC Root的引用鏈的時候,則證明此對象是不可達(dá)的,則會進行一次標(biāo)記。

GC Roots 包括:

虛擬機棧中引用的對象。

方法區(qū)中類靜態(tài)屬性實體引用的對象。

方法區(qū)中常量引用的對象。

本地方法棧中JNI引用的對象。

不可達(dá)不一定會立馬被回收,如果你重寫了Object.finalize()的話:

要知道這個方法是在對象不可達(dá)之后,GC時將該方法交給優(yōu)先級更低Finalizer線程去執(zhí)行(只執(zhí)行一次)。

同時該對象放入可回收隊列(虛引用出現(xiàn)的地方?)

并在下次GC時再次判斷是否可達(dá)以及finalize()是否被執(zhí)行,然后再回收。

現(xiàn)在是不建議覆蓋此方法的。畢竟影響GC。

GC 算法

復(fù)制算法

將區(qū)域分為兩塊,其中一塊閑置。GC時將可達(dá)的對象復(fù)制至閑置的區(qū)域,清除當(dāng)前區(qū)域。循環(huán)往復(fù)。

標(biāo)記-清除算法

先根據(jù)引用鏈標(biāo)記所有對象的存活狀態(tài),然后并從內(nèi)存中清除所有不可達(dá)對象進行清除

標(biāo)記-壓縮算法

標(biāo)記-清除算法的改進版,將所有存活的對象壓縮整理到同一側(cè)區(qū)域,較少內(nèi)存空間碎片。然后清理邊界外的區(qū)域。

Minor GC

針對新生代的GC, Eden滿了之后會觸發(fā)。

Minor GC時,會將eden和from區(qū)域的存活對象都復(fù)制至Surivor的to區(qū)域(假設(shè)當(dāng)前to閑置),當(dāng)對象在在surivor存在足夠久,比如熬過了默認(rèn)的15次GC,那么就會被存入老年代。

當(dāng)然若to區(qū)域空間放不下所有存活的對象,那么多余的都會進入老年代。

這過程顯然的用的是 復(fù)制算法。

Full GC

對所有空間進行GC,包括堆和方法區(qū)(類卸載)

觸發(fā)條件:

System.gc()

老年代的空間不足(包括新申請、從eden或surivor復(fù)制過來的)

方法區(qū)空間不足

老年代區(qū)域使用的是標(biāo)記-壓縮算法。

5.方法區(qū):

方法區(qū),概念上的區(qū)域,指明該區(qū)具有什么功能。不同的虛擬機有不同的實現(xiàn),儲存的內(nèi)存區(qū)域不定。

永久代與元空間:均是方法區(qū)的實現(xiàn)。永久代將大多數(shù)據(jù)放在堆內(nèi)存上,容易誘發(fā)OOM。

JDK 1.8之后廢棄永久代使用元空間,將方法區(qū)數(shù)據(jù)放在本地內(nèi)存,理論上可僅受系統(tǒng)內(nèi)存限制。

存放有運行時常量池,以及class加載后的產(chǎn)物(類字節(jié)碼、class/method/field等元數(shù)據(jù)對象、static-final常量、static變量),以及JIT過程的生成的代碼。線程共享

先說下,JIT, 即時編譯.JVM通過解釋器java字節(jié)碼(.class文件)執(zhí)行時,由于效率問題,引入JIT,即JVM發(fā)現(xiàn)某個代碼塊運行頻繁,則會將其編譯為相關(guān)的機器碼,存在方法區(qū),供下次使用。

運行時常量池

字節(jié)碼文件--class文件中包含了很多信息,比如魔數(shù)、屬性表、方法表等,其中還有一個常量池,常量池則包含了這個類所用到的各種字面量和引用量

class A {

final int a = 0;

String s = "hhh"

String s1 = "aa" + s

void test() {

String s2 = "ssss"

}

}

字面量

這個好理解,其實就是 各種 文本字符串,以及基本數(shù)據(jù)類型,final 常量等,注例如

那么 0、"hhh"、"aa"、"ssss" 都屬于字面量

引用量:

也就是符號引用,類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符

還是上面的Class A

那么 類全限定名

packagename.A、a、test、packagename.A.test:()V這些就是符號引用

類加載的時候,常量池就會被加載入運行時常量池中!

同時運行時常量池還具有動態(tài)性,比如

void test(String a) {

String s = new String("ddd")

s.intern();

}

那么判斷常量池是否已經(jīng)存在"ddd",若不存在然后將"ddd"加入常量池,存在則s指向常量池中"ddd"的地址。

最后

總結(jié)下,其實Java內(nèi)存結(jié)構(gòu) 也就是JVM內(nèi)存結(jié)構(gòu),由虛擬機規(guī)范定義。描述的是在程序執(zhí)行過程中,由JVM管理的不同數(shù)據(jù)區(qū)域,各個區(qū)域有特定的功能。

2. Java 內(nèi)存模型

稍微聊一下JMM,即 Java Memory Model, 這是一個抽象的概念,其模型圖如下。

JMM內(nèi)存模型

之前對JMM的概念真的分不清,以下整段拷貝...

Java內(nèi)存模型定義了線程和內(nèi)存的交互方式,在JMM抽象模型中,分為主內(nèi)存、工作內(nèi)存。主內(nèi)存是所有線程共享的,Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中;每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存的副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成。

我們知道,多線程之間的通信是通過共享內(nèi)存進行通信的

這里的主內(nèi)存可以說就是上面JVM中的共享內(nèi)存。而工作內(nèi)存則是一個抽象概念,其實現(xiàn)比如有什么CPU各級緩存,寄存器什么的

那么根據(jù)這多線程并發(fā)通信過程存在的可見性、原子性、指令重排問題,JMM則定義了一些語法集,也就是我們常見的synchronize、volatile等關(guān)鍵字。

emm...大致就先這樣吧,反正不要混淆與JVM內(nèi)存結(jié)構(gòu)這個概念吧。有時間再寫一篇并發(fā)通信的文章。

另外還可以了解下happen-before的規(guī)則

深入理解happens-before規(guī)則

3. Java對象模型

這個就指一個對象在內(nèi)存中的儲存結(jié)構(gòu)了。

Java 對象模型

jvm在加載class時,會創(chuàng)建instanceKlass,表示其元數(shù)據(jù),包括常量池、字段、方法等,存放在方法區(qū);instanceKlass是jvm中的數(shù)據(jù)結(jié)構(gòu);

在new一個對象時,jvm創(chuàng)建instanceOopDesc,來表示這個對象,存放在堆區(qū),其引用,存放在棧區(qū);它用來表示對象的實例信息,看起來像個指針實際上是藏在指針里的對象;instanceOopDesc對應(yīng)java中的對象實例;

instanceKlass對java上層來說并不可見。我們能看到就是根據(jù)instanceKlass而創(chuàng)建的Class對象。也就是 Object.getClass() 返回的這個玩意。

4. 問題:

1.堆內(nèi)存真的完全線程共享嗎?

線程在創(chuàng)建對象時,會進行內(nèi)存的分配,分配如何避免多個線程并發(fā)?就不提了,也不懂。但是一般由于需要頻繁的創(chuàng)建對象,故而使用了TLAB (Thread Loacle allocation Buffer)來提高效率,也就是每個線程給予先分配一小塊獨享的堆內(nèi)存,當(dāng)線程需要進行內(nèi)存分配時,則直接在該塊內(nèi)存進行分配。分配時,獨顯,分配后的對象是可以被其他線程讀取的。

TLAB存在于eden區(qū)域,也就是新生代,并不影響內(nèi)存回收。當(dāng)申請的內(nèi)存大于TLAB的剩余空間,其他策略處理。

2. 對象一定儲存在堆中分配內(nèi)存嗎?

在JIT過程中,會對代碼進行優(yōu)化,部分目的是為減少內(nèi)存分配壓力,其中用到了逃逸分析,即若分析得到某個對象的使用范圍不超過該方法或者不超過本線程,那么就可能會被優(yōu)化在棧上分配內(nèi)存!

當(dāng)然這里會考慮,棧本身大小就幾百K--1M的問題。還有就是逃逸分析,就是在對象是否為超出本線程或這個方法,那么該分析也可用于鎖消除優(yōu)化。

鎖消除優(yōu)化

比如下面這段代碼

fun test() {

val buffer = StringBuffer()

buffer.append("test")

}

大家都知道StringBuffer的方法都用了synchronize修飾,但是在經(jīng)過逃逸分析后,顯然buffer這個局部變量時本身就是線程安全,不可能被其他線程引用,那么JVM會自動StringBuffer對象的內(nèi)部鎖。可以說是當(dāng)做StringBuilder來看待。

參考文章

總結(jié)

以上是生活随笔為你收集整理的java 栈内存结构_JVM内存结构概念解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。