雙語網(wǎng)站建設(shè)報價濟(jì)南網(wǎng)絡(luò)seo公司
1 程序計數(shù)器
?
程序計數(shù)器是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。字節(jié)碼解釋器工作時通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等功能都需要依賴這個計數(shù)器來完。
?
java虛擬機(jī)的多線程是通過線程輪流切換并分配CPU的時間片的方式實現(xiàn)的,因此在任何時刻一個處理器(如果是多核處理器,則只是一個核)都只會處理一個線程,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨(dú)立的程序計數(shù)器,各線程之間計數(shù)器互不影響,獨(dú)立存儲,因此這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存。
?
從上面的介紹中我們知道程序計數(shù)器主要有兩個作用:
?
字節(jié)碼解釋器通過改變程序計數(shù)器來依次讀取指令,從而實現(xiàn)代碼的流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。
在多線程的情況下,程序計數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來的時候能夠知道該線程上次運(yùn)行到哪兒了。
注意:程序計數(shù)器是唯不會出現(xiàn) OutOfMemoryError 的內(nèi)存區(qū)域,它的生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而死亡。
?
2 Java 虛擬機(jī)棧
Java虛擬機(jī)棧也是線程私有的,它的生命周期和線程相同,描述的是 Java 方法執(zhí)行的內(nèi)存模型。Java虛擬機(jī)棧是由一個個棧幀組成,線程在執(zhí)行一個方法時,便會向棧中放入一個棧幀,每個棧幀中都擁有局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口信息。局部變量表主要存放了編譯器可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)和對象引用(reference類型,它不同于對象本身,可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔?#xff0c;也可能是指向一個代表對象的句柄或其他與此對象相關(guān)的位置)。
?
Java 虛擬機(jī)棧會出現(xiàn)兩種異常:StackOverFlowError 和 OutOfMemoryError。
?
StackOverFlowError:若Java虛擬機(jī)棧的內(nèi)存大小不允許動態(tài)擴(kuò)展,那么當(dāng)線程請求棧的深度超過當(dāng)前Java虛擬機(jī)棧的最大深度的時候,就拋出StackOverFlowError異常。
?
OutOfMemoryError:若 Java 虛擬機(jī)棧的內(nèi)存大小允許動態(tài)擴(kuò)展,且當(dāng)線程請求棧時內(nèi)存用完了,無法再動態(tài)擴(kuò)展了,此時拋出OutOfMemoryError異常。
?
Java 虛擬機(jī)棧也是線程私有的,每個線程都有各自的Java虛擬機(jī)棧,而且隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的死亡而死亡。
?
3 本地方法棧
和虛擬機(jī)棧所發(fā)揮的作用非常相似,區(qū)別是: 虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法 (也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)。 在 HotSpot 虛擬機(jī)中和 Java 虛擬機(jī)棧合二為一。
?
本地方法被執(zhí)行的時候,在本地方法棧也會創(chuàng)建一個棧幀,用于存放該本地方法的局部變量表、操作數(shù)棧、動態(tài)鏈接、出口信息。方法執(zhí)行完畢后相應(yīng)的棧幀也會出棧并釋放內(nèi)存空間,也會出現(xiàn) StackOverFlowError 和 OutOfMemoryError 兩種異常。
?
4 堆
堆是Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊,Java 堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例以及數(shù)組都在這里分配內(nèi)存(目前由于編譯器的優(yōu)化,對象在堆上分配已經(jīng)沒有那么絕對了,參見:https://www.cnblogs.com/aiqiqi/p/10650394.html)。
?
Java 堆是垃圾收集器管理的主要區(qū)域,因此也被稱作GC堆(Garbage Collected Heap)。從垃圾回收的角度,由于現(xiàn)在收集器基本都采用分代垃圾收集算法,所以Java堆還可以細(xì)分為:新生代和老年代:其中新生代又分為:Eden空間、From Survivor、To Survivor空間。進(jìn)一步劃分的目的是更好地回收內(nèi)存,或者更快地分配內(nèi)存?!胺执厥铡笔腔谶@樣一個事實:對象的生命周期不同,所以針對不同生命周期的對象可以采取不同的回收方式,以便提高回收效率。從內(nèi)存分配的角度來看,線程共享的java堆中可能會劃分出多個線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)。
?
?
?
如圖所示,JVM內(nèi)存主要由新生代、老年代、永久代構(gòu)成。
?
?、?新生代(Young Generation):大多數(shù)對象在新生代中被創(chuàng)建,其中很多對象的生命周期很短。每次新生代的垃圾回收(又稱Minor GC)后只有少量對象存活,所以選用復(fù)制算法,只需要少量的復(fù)制成本就可以完成回收。
?
新生代內(nèi)又分三個區(qū):一個Eden區(qū),兩個Survivor區(qū)(一般而言),大部分對象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時,還存活的對象將被復(fù)制到兩個Survivor區(qū)(中的一個)。當(dāng)這個Survivor區(qū)滿時,此區(qū)的存活且不滿足“晉升”條件的對象將被復(fù)制到另外一個Survivor區(qū)。對象每經(jīng)歷一次Minor GC,年齡加1,達(dá)到“晉升年齡閾值”后,被放到老年代,這個過程也稱為“晉升”。顯然,“晉升年齡閾值”的大小直接影響著對象在新生代中的停留時間,在Serial和ParNew GC兩種回收器中,“晉升年齡閾值”通過參數(shù)MaxTenuringThreshold設(shè)定,默認(rèn)值為15。
?
② 老年代(Old Generation):在新生代中經(jīng)歷了N次垃圾回收后仍然存活的對象,就會被放到年老代,該區(qū)域中對象存活率高。老年代的垃圾回收(又稱Major GC)通常使用“標(biāo)記-清理”或“標(biāo)記-整理”算法。整堆包括新生代和老年代的垃圾回收稱為Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都會同時收集整個GC堆,包括新生代)。
?
?、?永久代(Perm Generation):主要存放元數(shù)據(jù),例如Class、Method的元信息,與垃圾回收要回收的Java對象關(guān)系不大。相對于新生代和年老代來說,該區(qū)域的劃分對垃圾回收影響比較小。
?
在 JDK 1.8中移除整個永久代,取而代之的是一個叫元空間(Metaspace)的區(qū)域(永久代使用的是JVM的堆內(nèi)存空間,而元空間使用的是物理內(nèi)存,直接受到本機(jī)的物理內(nèi)存限制)。
?
5 方法區(qū)
方法區(qū)與 Java 堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應(yīng)該是與 Java 堆區(qū)分開來。
?
HotSpot 虛擬機(jī)中方法區(qū)也常被稱為 “永久代”,本質(zhì)上兩者并不等價。僅僅是因為 HotSpot 虛擬機(jī)設(shè)計團(tuán)隊用永久代來實現(xiàn)方法區(qū)而已,這樣 HotSpot 虛擬機(jī)的垃圾收集器就可以像管理 Java 堆一樣管理這部分內(nèi)存了。但是這并不是一個好主意,因為這樣更容易遇到內(nèi)存溢出問題。
?
相對而言,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入方法區(qū)后就“永久存在”了。
?
6 運(yùn)行時常量池
運(yùn)行時常量池是方法區(qū)的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有常量池信息(用于存放編譯期生成的各種字面量和符號引用)
?
既然運(yùn)行時常量池時方法區(qū)的一部分,自然受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請到內(nèi)存時會拋出 OutOfMemoryError 異常。
?
JDK1.7及之后版本的 JVM 已經(jīng)將運(yùn)行時常量池從方法區(qū)中移了出來,在 Java 堆(Heap)中開辟了一塊區(qū)域存放運(yùn)行時常量池。
?
?
?
7 直接內(nèi)存
直接內(nèi)存并不是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的一部分,也不是虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是這部分內(nèi)存也被頻繁地使用。而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。
?
JDK1.4中新加入的 NIO(New Input/Output) 類,引入了一種基于通道(Channel) 與緩存區(qū)(Buffer) 的 I/O 方式,它可以直接使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在 Java 堆中的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作。這樣就能在一些場景中顯著提高性能,因為避免了在 Java 堆和 Native 堆之間來回復(fù)制數(shù)據(jù)。
?
本機(jī)直接內(nèi)存的分配不會收到 Java 堆的限制,但是,既然是內(nèi)存就會受到本機(jī)總內(nèi)存大小以及處理器尋址空間的限制。