自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入進(jìn)階:圖解分析JVM內(nèi)存堆布局

云計(jì)算 虛擬化
Java虛擬機(jī)棧也是線程私有的,虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀,用于存放局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息。

[[258254]]

Java能夠?qū)崿F(xiàn)跨平臺(tái)的一個(gè)根本原因,是定義了class文件的格式標(biāo)準(zhǔn),凡是實(shí)現(xiàn)該標(biāo)準(zhǔn)的JVM都能夠加載并解釋該class文件,據(jù)此也可以知道,為啥Java語言的執(zhí)行速度比C/C++語言執(zhí)行的速度要慢了,當(dāng)然原因肯定不止這一個(gè),如在JVM中沒有數(shù)據(jù)寄存器,指令集使用的是棧來保存中間數(shù)據(jù)…等,盡管Java的貢獻(xiàn)者們?yōu)閳?zhí)行速度的提高想了各種辦法,如JIT、動(dòng)態(tài)編譯器等,以下是Leetcode中一道題目用不同的語言實(shí)現(xiàn)時(shí)的執(zhí)行性能對(duì)比圖…

以下是JVM的一個(gè)基本架構(gòu)圖,在這個(gè)基本架構(gòu)圖中,棧有兩部份,Java線程棧以及本地方法棧,棧的概念與C/C++程序基本上都是一個(gè)概念,里面存放的都是棧幀,一個(gè)棧幀代表的就是一個(gè)函數(shù)的調(diào)用,在棧幀里面存放了函數(shù)的形參,函數(shù)的局部變量, 返回地址等,但是與C/C++的一個(gè)重要區(qū)別是,C/C++里面有傳值以及傳址的區(qū)別,當(dāng)傳的是一個(gè)對(duì)象時(shí)( 結(jié)構(gòu)體也可以當(dāng)成對(duì)象,其實(shí)就是對(duì)象~,只不過里面的方法默認(rèn)都是public的,不信你可以試試,在結(jié)構(gòu)體中加一個(gè)函數(shù),編譯器也不會(huì)報(bào)錯(cuò),程序依舊運(yùn)行~~~),會(huì)將對(duì)象復(fù)到到棧中,而Java中只有基本類型才是傳值的,其他類型傳的都是引用,什么是引用,學(xué)過C/C++的就把引用當(dāng)作指針理解吧~~~,在這個(gè)基本架構(gòu)圖中,可以看出JVM還定義了一個(gè)本地方法棧,本地方法棧是為Java調(diào)用本地方法【這些本地方法是由其他語言編寫的】服務(wù)的。

上面的圖中看到的是JVM中棧有兩個(gè),但是堆只有一個(gè),每一個(gè)線程都有自已的線程?!揪€程棧的大小可以通過設(shè)置JVM的-xss參數(shù)進(jìn)行配置,32位系統(tǒng)下,JDK5.0以后每個(gè)線程堆棧大小為1M,以前每個(gè)線程堆棧大小為256K】,線程棧里面的數(shù)據(jù)屬于該線程私有,但是所有的線程都共享一個(gè)堆空間,堆中存放的是對(duì)象數(shù)據(jù),什么是對(duì)象數(shù)據(jù),排除法,排除基本類型以及引用類型以外的數(shù)據(jù)都將放在堆空間中。其中方法區(qū)和堆是所有線程共享的數(shù)據(jù)區(qū)。

1.程序計(jì)數(shù)器

在CPU的寄存器中有一個(gè)PC寄存器,存放下一條指令地址,這里,虛擬機(jī)不使用CPU的程序計(jì)數(shù)器,自己在內(nèi)存中設(shè)立一片區(qū)域來模擬CPU的程序計(jì)數(shù)器。只有一個(gè)程序計(jì)數(shù)器是不夠的,當(dāng)多個(gè)線程切換執(zhí)行時(shí),那就單個(gè)程序計(jì)數(shù)器就沒辦法了,虛擬機(jī)規(guī)范中指出,每一條線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器。注意,Java虛擬機(jī)中的程序計(jì)數(shù)器指向正在執(zhí)行的字節(jié)碼地址,而不是下一條。

2. Java虛擬機(jī)棧

Java虛擬機(jī)棧也是線程私有的,虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀,用于存放局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息。每一個(gè)方法從調(diào)用直到執(zhí)行完成的過程都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中的入棧到出棧的過程。我們平時(shí)把內(nèi)存分為堆內(nèi)存和棧內(nèi)存,其中的棧內(nèi)存就指的是虛擬機(jī)棧的局部變量表部分。局部變量表存放了編譯期可以知道的基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double),對(duì)象引用(可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔?,也可能指向一個(gè)代表對(duì)象的句柄或者其他與此對(duì)象相關(guān)的位置),和返回后所指向的字節(jié)碼的地址。其中64 位長度的long 和double 類型的數(shù)據(jù)會(huì)占用2個(gè)局部變量空間(Slot),其余的數(shù)據(jù)類型只占用1個(gè)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。當(dāng)遞歸層次太深時(shí),會(huì)引發(fā)java.lang.StackOverflowError,這是虛擬機(jī)棧拋出的異常。

3. 本地方法棧

在HotSpot虛擬機(jī)將本地方法棧和虛擬機(jī)棧合二為一,它們的區(qū)別在于,虛擬機(jī)棧為執(zhí)行Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。

4. Java堆

Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。這個(gè)區(qū)域是用來存放對(duì)象實(shí)例的,幾乎所有對(duì)象實(shí)例都會(huì)在這里分配內(nèi)存。堆是Java垃圾收集器管理的主要區(qū)域(GC堆),垃圾收集器實(shí)現(xiàn)了對(duì)象的自動(dòng)銷毀。Java堆可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的有Eden空間,F(xiàn)rom Survivor空間,To Survivor空間等。Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。可以通過-Xmx和-Xms控制。

5. 方法區(qū)

方法區(qū)也叫***代。在過去(自定義類加載器還不是很常見的時(shí)候),類大多是”static”的,很少被卸載或收集,因此被稱為“***的(Permanent)”。雖然Java 虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java 堆區(qū)分開來。同時(shí),由于類class是JVM實(shí)現(xiàn)的一部分,并不是由應(yīng)用創(chuàng)建的,所以又被認(rèn)為是“非堆(non-heap)”內(nèi)存。HotSpot 虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)選擇把GC 分代收集擴(kuò)展至方法區(qū),或者說使用***代來實(shí)現(xiàn)方法區(qū)而已。對(duì)于其他虛擬機(jī)(如BEA JRockit、IBM J9 等)來說是不存在***代的概念的。

***代也是各個(gè)線程共享的區(qū)域,它用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載過的類信息,常量,靜態(tài)變量(JDK7中被移到Java堆),即時(shí)編譯期編譯后的代碼(類方法)等數(shù)據(jù)。這里要講一下運(yùn)行時(shí)常量池,它是方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號(hào)引用(其實(shí)就是八大基本類型的包裝類型和String類型數(shù)據(jù)(JDK7中被移到Java堆))(官方文檔說明: In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application)。

在JDK1.7中的HotASpot中,已經(jīng)把原本放在方法區(qū)的字符串常量池移出。

  • 將interned String移到Java堆中
  • 將符號(hào)Symbols移到native memory(不受GC管理的內(nèi)存)

從JDK7開始***代的移除工作,貯存在***代的一部分?jǐn)?shù)據(jù)已經(jīng)轉(zhuǎn)移到了Java Heap或者是Native Heap。但***代仍然存在于JDK7,并沒有完全的移除:符號(hào)引用(Symbols)轉(zhuǎn)移到了native heap;字面量(interned strings)轉(zhuǎn)移到了java heap;類的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap。隨著JDK8的到來,JVM不再有PermGen。但類的元數(shù)據(jù)信息(metadata)還在,只不過不再是存儲(chǔ)在連續(xù)的堆空間上,而是移動(dòng)到叫做“Metaspace”的本地內(nèi)存(Native memory)中。

在JVM中共享數(shù)據(jù)空間劃分如下圖所示:

上圖中,刻畫了Java程序運(yùn)行時(shí)的堆空間,可以簡述成如下2條:

1.JVM中共享數(shù)據(jù)空間可以分成三個(gè)大區(qū),新生代(Young Generation)、老年代(Old Generation)、***代(Permanent Generation),其中JVM堆分為新生代和老年代。

2.新生代可以劃分為三個(gè)區(qū),Eden區(qū)(存放新生對(duì)象),兩個(gè)幸存區(qū)(From Survivor和To Survivor)(存放每次垃圾回收后存活的對(duì)象)。

3.***代管理class文件、靜態(tài)對(duì)象、屬性等(JVM uses a separate region of memory, called the Permanent Generation (orPermGen for short), to hold internal representations of java classes. PermGen is also used to store more information )。

4.JVM垃圾回收機(jī)制采用“分代收集”:新生代采用復(fù)制算法,老年代采用標(biāo)記清理算法。

作為操作系統(tǒng)進(jìn)程,Java 運(yùn)行時(shí)面臨著與其他進(jìn)程完全相同的內(nèi)存限制:操作系統(tǒng)架構(gòu)提供的可尋址地址空間和用戶空間。

操 作系統(tǒng)架構(gòu)提供的可尋址地址空間,由處理器的位數(shù)決定,32 位提供了 2^32 的可尋址范圍,也就是 4,294,967,296 位,或者說 4GB。而 64 位處理器的可尋址范圍明顯增大:2^64,也就是 18,446,744,073,709,551,616,或者說 16 exabyte(百億億字節(jié))。

地址空間被劃分為用戶空間和內(nèi)核空間。內(nèi)核是主要的操作系統(tǒng)程序和C運(yùn)行時(shí),包含用于連接計(jì)算機(jī)硬件、調(diào)度程序以及提供聯(lián)網(wǎng)和虛擬內(nèi)存等服務(wù)的邏輯和基于C的進(jìn)程(JVM)。除去內(nèi)核空間就是用戶空間,用戶空間才是 Java 進(jìn)程實(shí)際運(yùn)行時(shí)使用的內(nèi)存。

默認(rèn)情況下,32 位 Windows 擁有 2GB 用戶空間和 2GB 內(nèi)核空間。在一些 Windows 版本上,通過向啟動(dòng)配置添加 /3GB 開關(guān)并使用 /LARGEADDRESSAWARE 開關(guān)重新鏈接應(yīng)用程序,可以將這種平衡調(diào)整為 3GB 用戶空間和 1GB 內(nèi)核空間。在 32 位 Linux 上,默認(rèn)設(shè)置為 3GB 用戶空間和 1GB 內(nèi)核空間。一些 Linux 分發(fā)版提供了一個(gè)hugemem內(nèi)核,支持 4GB 用戶空間。為了實(shí)現(xiàn)這種配置,將進(jìn)行系統(tǒng)調(diào)用時(shí)使用的地址空間分配給內(nèi)核。通過這種方式增加用戶空間會(huì)減慢系統(tǒng)調(diào)用,因?yàn)槊看芜M(jìn)行系統(tǒng)調(diào)用時(shí),操作系統(tǒng)必須在地址空間之間復(fù)制數(shù)據(jù)并重置進(jìn)程地址-空間映射。

下圖為一個(gè)32 位 Java 進(jìn)程的內(nèi)存布局:

可尋址的地址空間總共有 4GB,OS 和 C 運(yùn)行時(shí)大約占用了其中的 1GB,Java 堆占用了將近 2GB,本機(jī)堆占用了其他部分。請(qǐng)注意,JVM 本身也要占用內(nèi)存,就像 OS 內(nèi)核和 C 運(yùn)行時(shí)一樣。

注意:

1. 上文提到的可尋址空間即指***地址空間。

2. 對(duì)于2GB的用戶空間,理論上Java堆內(nèi)存***為1.75G,但一旦Java線程的堆達(dá)到1.75G,那么就會(huì)出現(xiàn)本地堆的Out-Of-Memory錯(cuò)誤,所以實(shí)際上Java堆的***可使用內(nèi)存為1.5G。

在JVM運(yùn)行時(shí),可以通過配置以下參數(shù)改變整個(gè)JVM堆的配置比例:

  1. Java heap的大?。ㄐ律?老年代) 
  2.   -Xms堆的最小值 
  3.   -Xmx堆空間的***值 
  4. 新生代堆空間大小調(diào)整 
  5.   -XX:NewSize新生代的最小值 
  6.   -XX:MaxNewSize新生代的***值 
  7.   -XX:NewRatio設(shè)置新生代與老年代在堆空間的大小 
  8.   -XX:SurvivorRatio新生代中Eden所占區(qū)域的大小 
  9. ***代大小調(diào)整 
  10.   -XX:MaxPermSize 
  11. 其他 
  12.   -XX:MaxTenuringThreshold,設(shè)置將新生代對(duì)象轉(zhuǎn)到老年代時(shí)需要經(jīng)過多少次垃圾回收,但是仍然沒有被回收 

在上面的配置中,老年代所占空間的大小是由-XX:SurvivorRatio這個(gè)參數(shù)進(jìn)行配置的,看完了上面的JVM堆空間分配圖,可能會(huì)奇怪,為啥新生代空間要?jiǎng)澐譃槿齻€(gè)區(qū)Eden及兩個(gè)Survivor區(qū)?有何用意?為什么要這么分?要理解這個(gè)問題,就得理解一下JVM的垃圾收集機(jī)制(復(fù)制算法也叫copy算法),步驟如下:

復(fù)制(Copying)算法

將內(nèi)存平均分成A、B兩塊,算法過程:

1. 新生對(duì)象被分配到A塊中未使用的內(nèi)存當(dāng)中。當(dāng)A塊的內(nèi)存用完了, 把A塊的存活對(duì)象對(duì)象復(fù)制到B塊。

2. 清理A塊所有對(duì)象。

3. 新生對(duì)象被分配的B塊中未使用的內(nèi)存當(dāng)中。當(dāng)B塊的內(nèi)存用完了, 把B塊的存活對(duì)象對(duì)象復(fù)制到A塊。

4. 清理B塊所有對(duì)象。

5. goto 1。

優(yōu)點(diǎn):簡單高效。缺點(diǎn):內(nèi)存代價(jià)高,有效內(nèi)存為占用內(nèi)存的一半。

圖解說明如下所示:(圖中后觀是一個(gè)循環(huán)過程)

對(duì)復(fù)制算法進(jìn)一步優(yōu)化:使用Eden/S0/S1三個(gè)分區(qū)

平均分成A/B塊太浪費(fèi)內(nèi)存,采用Eden/S0/S1三個(gè)區(qū)更合理,空間比例為Eden:S0:S1==8:1:1,有效內(nèi)存(即可分配新生對(duì)象的內(nèi)存)是總內(nèi)存的9/10。

算法過程:

1. Eden+S0可分配新生對(duì)象。

2. 對(duì)Eden+S0進(jìn)行垃圾收集,存活對(duì)象復(fù)制到S1。清理Eden+S0。一次新生代GC結(jié)束。

3. Eden+S1可分配新生對(duì)象。

4. 對(duì)Eden+S1進(jìn)行垃圾收集,存活對(duì)象復(fù)制到S0。清理Eden+S1。二次新生代GC結(jié)束。

5. goto 1。

默認(rèn)Eden:S0:S1=8:1:1,因此,新生代中可以使用的內(nèi)存空間大小占用新生代的9/10,那么有人就會(huì)問,為什么不直接分成兩個(gè)區(qū),一個(gè)區(qū)占9/10,另一個(gè)區(qū)占1/10,這樣做的原因大概有以下幾種:

1.S0與S1的區(qū)間明顯較小,有效新生代空間為Eden+S0/S1,因此有效空間就大,增加了內(nèi)存使用率

2.有利于對(duì)象代的計(jì)算,當(dāng)一個(gè)對(duì)象在S0/S1中達(dá)到設(shè)置的XX:MaxTenuringThreshold值后,會(huì)將其分到老年代中,設(shè)想一下,如果沒有S0/S1,直接分成兩個(gè)區(qū),該如何計(jì)算對(duì)象經(jīng)過了多少次GC還沒被釋放,你可能會(huì)說,在對(duì)象里加一個(gè)計(jì)數(shù)器記錄經(jīng)過的GC次數(shù),或者存在一張映射表記錄對(duì)象和GC次數(shù)的關(guān)系,是的,可以,但是這樣的話,會(huì)掃描整個(gè)新生代中的對(duì)象, 有了S0/S1我們就可以只掃描S0/S1區(qū)了~~~

參考資料:http://my.oschina.net/hosee/blog/638753

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2020-05-27 21:13:27

JavaJVM內(nèi)存

2017-01-11 14:02:32

JVM源碼內(nèi)存

2010-09-27 08:38:49

JVM堆JVM棧

2010-09-27 09:31:42

JVM內(nèi)存結(jié)構(gòu)

2023-11-05 12:05:35

JVM內(nèi)存

2010-09-25 12:54:24

JVM內(nèi)存

2024-07-26 10:23:52

2022-08-30 07:00:18

執(zhí)行引擎Hotspot虛擬機(jī)

2018-04-17 14:41:41

Java堆內(nèi)存溢出

2018-11-01 10:34:37

JVM內(nèi)存配置

2022-07-03 20:31:59

JVMJava虛擬機(jī)

2015-12-28 11:41:57

JVM內(nèi)存區(qū)域內(nèi)存溢出

2021-11-26 00:00:48

JVM內(nèi)存區(qū)域

2015-08-06 14:54:50

JavaScript分析工具OneHeap

2022-12-26 14:41:38

Linux內(nèi)存

2021-09-24 08:10:40

Java 語言 Java 基礎(chǔ)

2022-07-06 08:05:52

Java對(duì)象JVM

2023-02-01 08:13:30

Redis內(nèi)存碎片

2010-04-27 09:17:23

內(nèi)存屏障JVM

2019-09-02 14:53:53

JVM內(nèi)存布局GC
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)