Java語言和虛擬機規(guī)范官方文檔:https://docs.oracle.com/javase/specs/index.html
概述
Java虛擬機在執(zhí)行程序的過程中,會把它管理的內(nèi)存分為不同的數(shù)據(jù)區(qū)域。其中一些數(shù)據(jù)區(qū)域是在Java虛擬機啟動時創(chuàng)建的,僅在Java虛擬機退出時才被銷毀。有些數(shù)據(jù)區(qū)域是每個線程私有的。在創(chuàng)建線程時創(chuàng)建每個線程的數(shù)據(jù)區(qū)域,并在線程退出時銷毀每個數(shù)據(jù)區(qū)域。如圖所示:

程序計數(shù)器
程序計數(shù)器可以看作當前線程所執(zhí)行的字節(jié)碼的行號指示器。
每個線程都需要一個獨立的程序計數(shù)器,各個線程之間的計數(shù)器互不影響獨立存儲,可以說這類內(nèi)存區(qū)域是線程私有的。
如果線程正在執(zhí)行的是Java方法,那么這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址。
如果線程正在執(zhí)行的是Native方法,那么這個計數(shù)器的值是Undefined。
此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutofMemoryError情況的區(qū)域。
Java虛擬機棧
Java虛擬機棧也是線程私有的,它是為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務。
每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。
每個方法從調(diào)用到執(zhí)行完成過程中,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。
局部變量表存放了編譯期可知的8種數(shù)據(jù)類型、對象引用和returnAddress類型(指向了一條字節(jié)碼指令地址)。
如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常。
如果虛擬機??梢詣討B(tài)擴展,擴展時無法申請到足夠的內(nèi)存,就會拋出OutOfMemoryError異常。
本地方法棧
本地方法棧是為虛擬機適用到的Native方法服務的。有的虛擬機(譬如sun HotSpot)直接就把本地方法棧和虛擬機棧合二為一。它拋出的異常與虛擬機棧類似。
Java堆
對大多數(shù)應用來說,Java堆是虛擬機管理的內(nèi)存中最大的一塊區(qū)域,是被所有線程共享的區(qū)域。官方文檔中寫道
The heap is the run-time data area from which memory for all class instances and arrays is allocated.
即所有的對象實例和數(shù)組都要在堆上分配。但隨著JIT編譯器發(fā)展,這并非絕對的。
Java堆書垃圾回收的主要區(qū)域。它還可以細分為:新生代和老年代;再細致一點有Eden空間、From Survivor空間、To Survivor空間。
如果在堆中沒有內(nèi)存可用,且堆無法再擴展時,就會拋出OutOfMemoryError異常。
方法區(qū)
方法區(qū)也是各個線程共享的內(nèi)存區(qū)域,用于存儲虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
官方文檔中只規(guī)定了方法區(qū)這個概念,但沒有規(guī)定具體的實現(xiàn)方式。在JDK1.7以前,HotSpot虛擬機主要使用永久代的方式來實現(xiàn)方法區(qū),JDK1.8開始改為元空間。
運行時常量池
運行時常量池是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項就是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容在類加載后進入方法區(qū)的運行時常量池存放。
運行時常量池相比于Class文件常量池,具備動態(tài)性。Java并不要求常量一定只有編譯期才能產(chǎn)生,運行期間也可能有新的常量放入池中。例如String類的intern()方法。
關(guān)于String類的intern方法,推薦一篇文章:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html