1 程序計數(shù)器(Program Counter Register)
程序計數(shù)器是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器穗酥。在虛擬機(jī)的概念模型里龄章,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令痊项,分支胳赌、循環(huán)咳榜、跳轉(zhuǎn)夏醉、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成涌韩。
由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實(shí)現(xiàn)的畔柔,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)都只會執(zhí)行一條線程中的指令臣樱。因此靶擦,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨(dú)立的程序計數(shù)器雇毫,各個線程之間的計數(shù)器互不影響玄捕,獨(dú)立存儲。
如果線程正在執(zhí)行一個Java方法棚放,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址枚粘;如果線程正在執(zhí)行一個Native方法,這個計數(shù)器的值為Undefined飘蚯。程序計數(shù)器是唯一一個在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域馍迄。
2 Java虛擬機(jī)棧(Java Virtual Machine Stacks)
Java虛擬機(jī)棧是線程私有的福也,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表攀圈、操作數(shù)棧暴凑、動態(tài)鏈接、方法出口等信息赘来。每一個方法被調(diào)用直至執(zhí)行完成的過程现喳,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
如果線程請求的棧深度大于虛擬機(jī)所允許的深度犬辰,將拋出StackOverflowError異常拿穴;如果虛擬機(jī)棧可以動態(tài)擴(kuò)展(當(dāng)前大部分的Java虛擬機(jī)都可動態(tài)擴(kuò)展忧风,只不過Java虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧)并且擴(kuò)展時無法申請到足夠的內(nèi)存默色,就會拋出OutOfMemoryError異常。
-Xss:每個線程的堆棧大小狮腿。
3 本地方法棧(Native Method Stack)
本地方法棧與虛擬機(jī)棧所發(fā)揮的作用非常相似腿宰,虛擬機(jī)棧為虛擬機(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ī)棧一樣,本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常英遭。
4 Java堆(Java Heap)
對于大多數(shù)應(yīng)用來說间护,Java堆是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域挖诸,在虛擬機(jī)啟動時創(chuàng)建汁尺。此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存多律。
Java堆是垃圾收集器管理的主要區(qū)域痴突,因此很多時候也被稱作“GC堆”。從內(nèi)存回收的角度來看狼荞,由于現(xiàn)在收集器基本都采用分代收集算法辽装,所以Java堆中還可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的有Eden空間相味、From Survivor空間拾积、To Survivor空間等。從內(nèi)存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(qū)殷勘。
Java堆可以處于物理上不連續(xù)的內(nèi)存空間中此再,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣玲销。在實(shí)現(xiàn)時输拇,既可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的贤斜,不過當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的(通過-Xmx和-Xms控制)策吠。如果在堆中沒有內(nèi)存完成實(shí)例分配,并且堆也無法再擴(kuò)展時瘩绒,將會拋出OutOfMemoryError異常猴抹。
-Xms:start,初始值锁荔。
-Xmx:max蟀给,最大值。
5 方法區(qū)
方法區(qū)是各個線程共享的內(nèi)存區(qū)域阳堕,它用于存儲已被虛擬機(jī)加載的類信息跋理、常量、靜態(tài)變量恬总、即時編譯器編譯后的代碼等數(shù)據(jù)前普。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆)壹堰,目的應(yīng)該是與Java堆區(qū)分開來拭卿。
當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常贱纠。
在JDK1.7之前峻厚,HotSpot虛擬機(jī)使用永久代來實(shí)現(xiàn)方法區(qū)。
在JDK1.7之中并巍,HotSpot虛擬機(jī)將原本放在永久代的字符串常量池移到了Java堆中目木;在JDK1.8之中,字符串常量池依然還在Java堆中懊渡。
在JDK1.7之后,HotSpot虛擬機(jī)使用元空間(Metaspace)替代永久代來實(shí)現(xiàn)方法區(qū)军拟。
元空間和永久代之間最大的區(qū)別:元空間使用本地內(nèi)存剃执,永久代使用JVM的內(nèi)存。因此懈息,默認(rèn)情況下肾档,元空間的大小僅受本地內(nèi)存限制。
6 運(yùn)行時常量池
運(yùn)行時常量池是方法區(qū)的一部分。Class文件中除了有類的版本怒见、字段俗慈、方法、接口等描述信息外遣耍,還有一項信息是常量池闺阱,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時常量池中舵变。
運(yùn)行時常量池相對于Class文件常量池的另外一個重要特征是具備動態(tài)性酣溃,Java語言并不要求常量一定只有編譯器才能產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時常量池纪隙,運(yùn)行期間也可能將新的常量放入池中赊豌,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。
既然運(yùn)行時常量池是方法區(qū)的一部分绵咱,自然受到方法區(qū)內(nèi)存的限制碘饼,當(dāng)常量池?zé)o法再申請到內(nèi)存時會拋出OutOfMemoryError異常。
7 直接內(nèi)存
直接內(nèi)存并不是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的一部分悲伶,也不是Java虛擬機(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)行操作。這樣能在一些場景中顯著提高性能主穗,因?yàn)楸苊饬嗽贘ava堆和Native堆中來回復(fù)制數(shù)據(jù)泻拦。
8 intern方法
https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html