1、虚拟机内存管理、运行时数据区、线程共享区、Java堆、新生代、老年代、Eden区域分配、方法区、线程独占区、虚拟机栈
1.Java虛擬機(jī)內(nèi)存管理
1.1.運(yùn)行時(shí)數(shù)據(jù)區(qū)[Runtime Data Area]
1.1.1.線程共享區(qū)
1.1.1.1.Java堆[heap]
1.1.1.1.1.新生代、老年代、Eden區(qū)域分配
1.1.1.1.2.年輕代(Young Generation)
1.1.1.1.3.老年代(Old Generation)
1.1.1.2.方法區(qū)[Method Area]
1.1.2.線程獨(dú)占區(qū)
1.1.2.1.虛擬機(jī)棧[VM Stack]
1.1.2.2.本地方法棧[Native Method stack]
1.1.2.3.程序計(jì)數(shù)器[Program Counter Register]
1.1.3.執(zhí)行引擎
1.Java虛擬機(jī)內(nèi)存管理
JVM = 類加載器(classloader)+執(zhí)行引擎(execution engine)+運(yùn)行時(shí)數(shù)據(jù)區(qū)域(runtime data area)
再如網(wǎng)上的一個(gè)圖:
方法區(qū)和堆是所有線程共享的:
1.1.運(yùn)行時(shí)數(shù)據(jù)區(qū)[Runtime Data Area]
Java虛擬機(jī)在執(zhí)行Java程序的過程中會把它管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途,以及創(chuàng)建和銷毀的時(shí)間,有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動而存在,有些區(qū)域則是依賴用戶線程的啟動和結(jié)束而建立和銷毀。
1.1.1.線程共享區(qū)
1.1.1.1.Java堆[heap]
?存放對象實(shí)例。
?垃圾收集器管理的主要區(qū)域。
?新生代,老年代,Eden空間。
為java內(nèi)存管理的最大一塊兒區(qū)域:
堆不夠出現(xiàn):OutOfMemory
主要的參數(shù):-Xmx -Xms
被所有線程共享,在虛擬機(jī)啟動時(shí)創(chuàng)建,用來存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存。對于大多數(shù)應(yīng)用來說,Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是垃圾收集器管理的主要區(qū)域,因此很多時(shí)候也被稱做**”GC堆”**。如果從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都是采用的分代收集算法,所以Java堆中還可以細(xì)分為:新生代和老年代;新生代又有Eden空間、From Survivor空間、To Survivor空間三部分。Java 堆不需要連續(xù)內(nèi)存,并且可以通過動態(tài)增加其內(nèi)存,增加失敗會拋出 OutOfMemoryError異常。
對于堆區(qū)大小,可以通過參數(shù)-Xms和-Xmx來控制,-Xms為JVM啟動時(shí)申請的最小heap內(nèi)存,默認(rèn)為物理內(nèi)存的1/64但小于1GB;-Xmx為JVM可申請的最大Heap內(nèi)存,默認(rèn)為物理內(nèi)存的1/4但小于1GB,默認(rèn)當(dāng)剩余堆空間小于40%時(shí),JVM會增大Heap到-Xmx大小,可通過-XX:MinHeapFreeRadio參數(shù)來控制這個(gè)比例;當(dāng)空余堆內(nèi)存大于70%時(shí),JVM會減少Heap大小到-Xms指定大小,可通過-XX:MaxHeapFreeRatio來指定這個(gè)比例。對于系統(tǒng)而言,為了避免在運(yùn)行期間頻繁的調(diào)整Heap大小,我們通常將-Xms和-Xmx設(shè)置成一樣。
1.1.1.1.1.新生代、老年代、Eden區(qū)域分配
Java中的堆是JVM所管理的最大的一塊內(nèi)存空間,主要用于存放各種類的實(shí)例對象。
在java中,堆被劃分成兩個(gè)不同的區(qū)域:新生代(Young)、老年代(Old)。新生代(Young)又被劃分為三個(gè)區(qū)域:Eden、From Survivor、To Survivor。
這樣劃分的目的是為了使JVM能夠更好的管理堆內(nèi)存中的對象,包括內(nèi)存的分配以及回收。
堆的內(nèi)存模型大致為:
從圖中可以看出:堆大小=新生代+老年代。其中,堆的大小可以通過參數(shù)-Xms、-Xmx來指定。
以JDK1.6為例:
默認(rèn)的,新生代(Young)與老年代(Old)的比例的值為1:2(該值可以通過-XX:NewRatio來指定),即:新生代(Young)=1/3的堆空間大小。老年代(Old)=2/3的堆空間大小。其中,新生代(Young)被細(xì)分為Eden和兩個(gè)Survivor區(qū)域,這兩個(gè)Survivor區(qū)域分別被命名為from和to,以示區(qū)分。Eden空間不足的時(shí)候,會把存活的對象轉(zhuǎn)移到Survivor中。
新生代大小可以由-Xmn來控制,默認(rèn)的Eden: from: to = 8:1:1(可以通過參數(shù)-XX:SurvivorRatio來設(shè)定),即:Eden = 8/10的新生代空間大小,from = to = 1/10的新生代空間大小。
JVM每次只會使用Eden和其中的一塊Survivor區(qū)域來為對象服務(wù),所以無論什么時(shí)候,總是有一塊Survivor區(qū)域是空閑著的。
因此,新生代實(shí)際可用的內(nèi)存空間為9/10 (即90%)的新生代空間。
1.1.1.1.2.年輕代(Young Generation)
對象在被創(chuàng)建時(shí),內(nèi)存首先是在年輕代進(jìn)行分配(注意,大對象可以直接在老年代分配)。當(dāng)年輕代需要回收時(shí)會觸發(fā)Minor GC(也稱作Young GC)。
年輕代由Eden Space和兩塊相同大小的Survivor Space(又稱S0和S1)構(gòu)成,可通過-Xmn參數(shù)來調(diào)整新生代大小,也可通過-XX:SurvivorRatio來調(diào)整Eden Space和Survivor Space大小。不同的GC方式會按不同的方式來按比值劃分Eden Space和Survivor Space,有些GC方式還會根據(jù)運(yùn)行狀況來動態(tài)調(diào)整Eden、S0、S1的大小。
年輕代的Eden區(qū)內(nèi)存是連續(xù)的,所以其分配會非常快;同樣Eden區(qū)的回收也非常快(因?yàn)榇蟛糠智闆r下Eden區(qū)對象存活時(shí)間非常短,而Eden區(qū)采用的復(fù)制回收算法,此算法在存活比例很少的情況下非常高效,后面會詳細(xì)介紹)。
如果在執(zhí)行垃圾回收之后,仍沒有足夠的內(nèi)存分配,也不能再擴(kuò)展,將會拋出OutOfMemoryError:java Heap Space異常。
1.1.1.1.3.老年代(Old Generation)
老年代用于存放在年輕代中經(jīng)多次垃圾回收仍然存活的對象,可以理解為比較老一點(diǎn)的對象,例如緩存對象;新建的對象也有可能在老年代上直接分配內(nèi)存,這主要有兩種情況:一種為大對象,可以通過啟動參數(shù)設(shè)置-XX:PretenureSizeThreshold=1024,表示超過多大時(shí)就不在年輕代分配,而是直接在老年代分配。此參數(shù)在年輕代采用Parallel Scavenge GC時(shí)無效,因?yàn)槠鋾鶕?jù)運(yùn)行情況自己決定什么對象直接在老年代上分配內(nèi)存;另一種為大的數(shù)組對象,且數(shù)組對象中無引用外部對象。
當(dāng)老年代滿了的時(shí)候就需要對老年代進(jìn)行垃圾回收,老年代的垃圾回收稱作Major GC(也稱作Full GC)。
老年代所占用的內(nèi)存大小為-Xmx對應(yīng)的值減去-Xmn對應(yīng)的值。
1.1.1.2.方法區(qū)[Method Area]
方法區(qū)存放了要加載的類的信息(如類名,修飾符)、運(yùn)行時(shí)常量池、已被虛擬機(jī)加載的類信息、final定義的常量、屬性(類中的field)和方法信息、靜態(tài)變量、即時(shí)編譯后的代碼等數(shù)據(jù)。
當(dāng)開發(fā)人員調(diào)用類對象中的getName、isInterface等方法來獲取信息時(shí),這些數(shù)據(jù)都來源于方法區(qū)。方法區(qū)是全局共享的,在一定條件下它也會被GC。當(dāng)方法區(qū)使用的內(nèi)存超過它允許的大小時(shí),就會拋出OutOfMemory:PermGen Space異常。
在Hotspot虛擬機(jī)中,這塊區(qū)域?qū)?yīng)的是Permanent Generation(持久代),一般的,方法區(qū)上執(zhí)行的垃圾收集是很少的,因此方法區(qū)又被稱為持久代的原因之一,但這也不代表著在方法區(qū)上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的內(nèi)存回收和對已加載類的卸載。在方法區(qū)上進(jìn)行垃圾收集,條件苛刻而且相當(dāng)困難,關(guān)于其回后面再介紹。
**運(yùn)行時(shí)常量池(Runtime Constant Pool)**是方法區(qū)的一部分,用于存儲編譯期就生成的字面常量、符號引用、翻譯出來的直接引用(符號引用就是編碼是用字符串表示某個(gè)變量、接口的位置,直接引用就是根據(jù)符號引用翻譯出來的地址,將在類鏈接階段完成翻譯);運(yùn)行時(shí)常量池除了存儲編譯期常量外,也可以存儲在運(yùn)行時(shí)間產(chǎn)生的常量,比如String類的intern()方法,作用是String維護(hù)了一個(gè)常量池,如果調(diào)用的字符“abc”已經(jīng)在常量池中,則返回池中的字符串地址,否則,新建一個(gè)常量加入池中,并返回地址。
JVM方法區(qū)的相關(guān)參數(shù),最小值:–XX:PermSize;最大值–XX:MaxPermSize。
JVM用永久代(Permanet Generation)來存放方法區(qū)。可以通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。
和Java 堆一樣不需要連續(xù)的內(nèi)存,并且可以動態(tài)擴(kuò)展,動態(tài)擴(kuò)展失敗一樣會拋出 OutOfMemoryError異常。對這塊區(qū)域進(jìn)行垃圾回收的主要目標(biāo)是對常量池的回收和對類的卸載,但是一般比較難實(shí)現(xiàn),HotSpot虛擬機(jī)把它當(dāng)成永久代(Permanent Generation)來進(jìn)行垃圾回收。方法區(qū)邏輯上屬于堆的一部分,但是為了與堆進(jìn)行區(qū)分,通常又叫”非堆”。運(yùn)行時(shí)常量池(Runtime Constant Pool)運(yùn)行時(shí)常量池是方法區(qū)的一部分。Class文件中的常量池(編譯器生成的各種字面量和符號引用)會在類加載后被放入這個(gè)區(qū)域。除了在編譯期生成的常量,還允許動態(tài)生成,例如 String 類的intern()。這部分常量也會被放入運(yùn)行時(shí)常量池。
注:在JDK1.7之前,HotSpot使用永久代實(shí)現(xiàn)方法區(qū);HotSpot使用 GC分代實(shí)現(xiàn)方法區(qū)帶來了很大便利;從JDK1.7開始HotSpot開始移除永久代。其中符號引用(Symbols)被移動到Native Heap中,字符串常量和類引用被移動到Java Heap中。在 JDK1.8 中,永久代已完全被元空間(Meatspace)所取代。元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制。直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。在JDK 1.4中新加入了NIO類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲在Java堆里的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場景中顯著提高性能,因?yàn)楸苊饬嗽贘ava 堆和Native堆中來回復(fù)制數(shù)據(jù)。
1.1.2.線程獨(dú)占區(qū)
1.1.2.1.虛擬機(jī)棧[VM Stack]
線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會同時(shí)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型),它不等同于對象本身,根據(jù)不同的虛擬機(jī)實(shí)現(xiàn),它可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔?#xff0c;也可能指向一個(gè)代表對象的句柄或者其他與此對象相關(guān)的位置)和return Address類型(指向了一條字節(jié)碼指令的地址)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會改變局部變量表的大小。該區(qū)域可能拋出以下異常:
當(dāng)線程請求的棧深度超過最大值,會拋出StackOverflowError異常;棧進(jìn)行動態(tài)擴(kuò)展時(shí)如果無法申請到足夠內(nèi)存,會拋出OutOfMemoryError異常。
存放方法運(yùn)行時(shí)所需的數(shù)據(jù),成為棧幀。
- 虛擬機(jī)棧描述的是Java方法執(zhí)行的動態(tài)內(nèi)存模型。
- 棧幀
?每個(gè)方法執(zhí)行,都會創(chuàng)建一個(gè)棧幀,伴隨著方法從創(chuàng)建到執(zhí)行完成。用于存儲局部變量表,操作數(shù)棧,動態(tài)鏈接,方法出口等。
方法執(zhí)行的時(shí)候入棧,方法執(zhí)行完成時(shí)出棧:
?局部變量表
- 存放編譯器可知的各種基本數(shù)據(jù)類型,引用類型,return Address類型。
- 局部變量表的內(nèi)存空間在編譯期完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀分配多少內(nèi)存是固定的,在方法運(yùn)行期間是不會改變局部變量表的大小。
?大小
- StackOverflowError
- OutOfMemory
案例:
虛擬機(jī)棧占用的是操作系統(tǒng)內(nèi)存,每個(gè)線程都對應(yīng)著一個(gè)虛擬機(jī)棧,它是線程私有的,而且分配非常高效。一個(gè)線程的每個(gè)方法在執(zhí)行的同時(shí),都會創(chuàng)建一個(gè)棧幀(Statck Frame),棧幀中存儲的有局部變量表、操作站、動態(tài)鏈接、方法出口等,當(dāng)方法被調(diào)用時(shí),棧幀在JVM棧中入棧,當(dāng)方法執(zhí)行完成時(shí),棧幀出棧。
局部變量表中存儲著方法的相關(guān)局部變量,包括各種基本數(shù)據(jù)類型,對象的引用,返回地址等。在局部變量表中,只有l(wèi)ong和double類型會占用2個(gè)局部變量空間(Slot,對于32位機(jī)器,一個(gè)Slot就是32個(gè)bit),其它都是1個(gè)Slot。需要注意的是,局部變量表是在編譯時(shí)就已經(jīng)確定好的,方法運(yùn)行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內(nèi)都不會改變。
虛擬機(jī)棧中定義了兩種異常,如果線程調(diào)用的棧深度大于虛擬機(jī)允許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多數(shù)Java虛擬機(jī)都允許動態(tài)擴(kuò)展虛擬機(jī)棧的大小(有少部分是固定長度的),所以線程可以一直申請棧,直到內(nèi)存不足,此時(shí),會拋出OutOfMemoryError(內(nèi)存溢出)。
1.1.2.2.本地方法棧[Native Method stack]
HotSport虛擬機(jī)不區(qū)分,虛擬機(jī)棧和本地方法棧。
?虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù)。
?本地方法棧為虛擬機(jī)執(zhí)行native方法服務(wù)。
為JVM所調(diào)用到的Native即本地方法服務(wù)。
與虛擬機(jī)棧非常相似,其區(qū)別不過是虛擬機(jī)棧執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為了虛擬機(jī)使用到的Native方法服務(wù)。虛擬機(jī)規(guī)范中對本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。甚至有的虛擬機(jī)(譬如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。
本地方法棧用于支持native方法的執(zhí)行,存儲了每個(gè)native方法調(diào)用的狀態(tài)。本地方法棧和虛擬機(jī)方法棧運(yùn)行機(jī)制一致,它們唯一的區(qū)別就是,虛擬機(jī)棧是執(zhí)行Java方法的,而本地方法棧是用來執(zhí)行native方法的,在很多虛擬機(jī)中(如Sun的JDK默認(rèn)的HotSpot虛擬機(jī)),會將本地方法棧與虛擬機(jī)棧放在一起使用。
1.1.2.3.程序計(jì)數(shù)器[Program Counter Register]
?程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。
?程序計(jì)數(shù)器處于線程獨(dú)占區(qū)。
?如果線程執(zhí)行的是Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。如果正在執(zhí)行的是native方法,這個(gè)計(jì)數(shù)器的值為undefined。
?此區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
行號計(jì)數(shù)器如下:
記錄當(dāng)前線程所執(zhí)行到的字節(jié)碼的行號。
線程私有,它的生命周期與線程相同。可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機(jī)的概念模型里(僅是概念模型,各種虛擬機(jī)可能會通過一些更高效的方式去實(shí)現(xiàn)),字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,如:分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)(多線程切換)等基礎(chǔ)功能。如果線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie方法,這個(gè)計(jì)數(shù)器值則為空(undefined)。程序計(jì)數(shù)器中存儲的數(shù)據(jù)所占空間的大小不會隨程序的執(zhí)行而發(fā)生改變,所以此區(qū)域不會出現(xiàn)OutOfMemoryError的情況。
程序計(jì)數(shù)器是一個(gè)比較小的內(nèi)存區(qū)域,可能是CPU寄存器或者操作系統(tǒng)內(nèi)存,其主要用于指示當(dāng)前線程所執(zhí)行的字節(jié)碼執(zhí)行到了第幾行,可以理解為是當(dāng)前線程的行號指示器。字節(jié)碼解釋器在工作時(shí),會通過改變這個(gè)計(jì)數(shù)器的值來取下一條語句指令。 每個(gè)程序計(jì)數(shù)器只用來記錄一個(gè)線程的行號,所以它是線程私有(一個(gè)線程就有一個(gè)程序計(jì)數(shù)器)的。
如果程序執(zhí)行的是一個(gè)Java方法,則計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址;如果正在執(zhí)行的是一個(gè)本地(native,由C語言編寫完成)方法,則計(jì)數(shù)器的值為Undefined,由于程序計(jì)數(shù)器只是記錄當(dāng)前指令地址,所以不存在內(nèi)存溢出的情況,因此,程序計(jì)數(shù)器也是所有JVM內(nèi)存區(qū)域中唯一一個(gè)沒有定義OutOfMemoryError的區(qū)域。
1.1.3.執(zhí)行引擎
?即時(shí)編譯器[JITCompiler]
?垃圾收集[Garbage Collection]
1.1.4.本地庫接口[java Native Interface]
1.1.5.本地方法庫
1.1.6.類加載器子系統(tǒng)[classloader subsystem]
總結(jié)
以上是生活随笔為你收集整理的1、虚拟机内存管理、运行时数据区、线程共享区、Java堆、新生代、老年代、Eden区域分配、方法区、线程独占区、虚拟机栈的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 普兰金融是干什么的
- 下一篇: java美元兑换,(Java实现) 美元