一. 先來(lái)看看JVM運(yùn)行時(shí)候的內(nèi)存區(qū)域
大多數(shù) JVM 將內(nèi)存區(qū)域劃分為 Method Area(Non-Heap)(方法區(qū)),Heap(堆),Program Counter Register(程序計(jì)數(shù)器), VM Stack(虛擬機(jī)棧建钥,也有翻譯成JAVA 方法棧的),Native Method Stack (本地方法棧)只盹,其中Method Area和Heap是線程共享的蝴韭,VM Stack峭状,Native Method Stack 和Program Counter Register是非線程共享的。為什么分為線程共享和非線程共享的呢?請(qǐng)繼續(xù)往下看。
首先我們熟悉一下一個(gè)一般性的 Java 程序的工作過(guò)程。一個(gè) Java 源程序文件,會(huì)被編譯為字節(jié)碼文件(以 class 為擴(kuò)展名)捺檬,每個(gè)java程序都需要運(yùn)行在自己的JVM上,然后告知 JVM 程序的運(yùn)行入口贸铜,再被 JVM 通過(guò)字節(jié)碼解釋器加載運(yùn)行堡纬。那么程序開(kāi)始運(yùn)行后,都是如何涉及到各內(nèi)存區(qū)域的呢蒿秦?
概括地說(shuō)來(lái)烤镐,JVM初始運(yùn)行的時(shí)候都會(huì)分配好Method Area(方法區(qū))和Heap(堆),而JVM 每遇到一個(gè)線程棍鳖,就為其分配一個(gè)Program Counter Register(程序計(jì)數(shù)器), VM Stack(虛擬機(jī)棧)和Native Method Stack (本地方法棧)炮叶,當(dāng)線程終止時(shí),三者(虛擬機(jī)棧渡处,本地方法棧和程序計(jì)數(shù)器)所占用的內(nèi)存空間也會(huì)被釋放掉镜悉。這也是為什么我把內(nèi)存區(qū)域分為線程共享和非線程共享的原因,非線程共享的那三個(gè)區(qū)域的生命周期與所屬線程相同医瘫,而線程共享的區(qū)域與JAVA程序運(yùn)行的生命周期相同侣肄,所以這也是系統(tǒng)垃圾回收的場(chǎng)所只發(fā)生在線程共享的區(qū)域(實(shí)際上對(duì)大部分虛擬機(jī)來(lái)說(shuō)知發(fā)生在Heap上)的原因。
1. 程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊較小的內(nèi)存區(qū)域醇份,作用可以看做是當(dāng)前線程執(zhí)行的字節(jié)碼的位置指示器稼锅。分支吼具、循環(huán)、跳轉(zhuǎn)矩距、異常處理和線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)算器來(lái)完成拗盒,不多說(shuō)。
2.VM Strack
先來(lái)了解下JAVA指令的構(gòu)成:
JAVA指令由 操作碼 (方法本身)和 操作數(shù) (方法內(nèi)部變量) 組成锥债。
1)方法本身是指令的操作碼部分陡蝇,保存在Stack中;
2)方法內(nèi)部變量(局部變量)作為指令的操作數(shù)部分哮肚,跟在指令的操作碼之后毅整,保存在Stack中(實(shí)際上是簡(jiǎn)單類型(int,byte,short 等)保存在Stack中,對(duì)象類型在Stack中保存地址绽左,在Heap 中保存值);
虛擬機(jī)棧也叫棧內(nèi)存艇潭,是在線程創(chuàng)建時(shí)創(chuàng)建拼窥,它的生命期是跟隨線程的生命期,線程結(jié)束棧內(nèi)存也就釋放蹋凝,對(duì)于棧來(lái)說(shuō)不存在垃圾回收問(wèn)題鲁纠,只要線程一結(jié)束,該棧就 Over鳍寂,所以不存在垃圾回收改含。也有一些資料翻譯成JAVA方法棧,大概是因?yàn)樗枋龅氖莏ava方法執(zhí)行的內(nèi)存模型迄汛,每個(gè)方法執(zhí)行的同時(shí)創(chuàng)建幀棧(Strack Frame)用于存儲(chǔ)局部變量表(包含了對(duì)應(yīng)的方法參數(shù)和局部變量)捍壤,操作棧(Operand Stack,記錄出棧鞍爱、入棧的操作)鹃觉,動(dòng)態(tài)鏈接、方法出口等信息睹逃,每個(gè)方法被調(diào)用直到執(zhí)行完畢的過(guò)程盗扇,對(duì)應(yīng)這幀棧在虛擬機(jī)棧的入棧和出棧的過(guò)程。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean沉填、byte疗隶、char、short翼闹、int斑鼻、float、long橄碾、double)卵沉、對(duì)象的引用(reference類型颠锉,不等同于對(duì)象本身,根據(jù)不同的虛擬機(jī)實(shí)現(xiàn)史汗,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔樓砺樱部赡苁且粋€(gè)代表對(duì)象的句柄或者其他與對(duì)象相關(guān)的位置)和 returnAdress類型(指向下一條字節(jié)碼指令的地址)。局部變量表所需的內(nèi)存空間在編譯期間完成分配停撞,在方法在運(yùn)行之前瓷蛙,該局部變量表所需要的內(nèi)存空間是固定的,運(yùn)行期間也不會(huì)改變戈毒。
棧幀是一個(gè)內(nèi)存區(qū)塊艰猬,是一個(gè)數(shù)據(jù)集,是一個(gè)有關(guān)方法(Method)和運(yùn)行期數(shù)據(jù)的數(shù)據(jù)集埋市,當(dāng)一個(gè)方法 A 被調(diào)用時(shí)就產(chǎn)生了一個(gè)棧幀 F1冠桃,并被壓入到棧中,A 方法又調(diào)用了 B 方法道宅,于是產(chǎn)生棧幀 F2 也被壓入棧食听,執(zhí)行完畢后,先彈出 F2棧幀污茵,再?gòu)棾?F1 棧幀樱报,遵循“先進(jìn)后出”原則。光說(shuō)比較枯燥泞当,我們看一個(gè)圖來(lái)理解一下 Java棧迹蛤,如下圖所示:
3.Heap
Heap(堆)是JVM的內(nèi)存數(shù)據(jù)區(qū)。Heap 的管理很復(fù)雜襟士,是被所有線程共享的內(nèi)存區(qū)域盗飒,在JVM啟動(dòng)時(shí)候創(chuàng)建,專門用來(lái)保存對(duì)象的實(shí)例陋桂。在Heap 中分配一定的內(nèi)存來(lái)保存對(duì)象實(shí)例箩兽,實(shí)際上也只是保存對(duì)象實(shí)例的屬性值,屬性的類型和對(duì)象本身的類型標(biāo)記等章喉,并不保存對(duì)象的方法(以幀棧的形式保存在Stack中),在Heap 中分配一定的內(nèi)存保存對(duì)象實(shí)例汗贫。而對(duì)象實(shí)例在Heap 中分配好以后,需要在Stack中保存一個(gè)4字節(jié)的Heap 內(nèi)存地址秸脱,用來(lái)定位該對(duì)象實(shí)例在Heap 中的位置落包,便于找到該對(duì)象實(shí)例,是垃圾回收的主要場(chǎng)所摊唇。java堆處于物理不連續(xù)的內(nèi)存空間中咐蝇,只要邏輯上連續(xù)即可。
4.Method Area
Object Class Data(加載類的類定義數(shù)據(jù)) 是存儲(chǔ)在方法區(qū)的巷查。除此之外有序,常量抹腿、靜態(tài)變量、JIT(即時(shí)編譯器)編譯后的代碼也都在方法區(qū)旭寿。正因?yàn)榉椒▍^(qū)所存儲(chǔ)的數(shù)據(jù)與堆有一種類比關(guān)系警绩,所以它還被稱為 Non-Heap。方法區(qū)也可以是內(nèi)存不連續(xù)的區(qū)域組成的盅称,并且可設(shè)置為固定大小肩祥,也可以設(shè)置為可擴(kuò)展的,這點(diǎn)與堆一樣缩膝。
垃圾回收在這個(gè)區(qū)域會(huì)比較少出現(xiàn)混狠,這個(gè)區(qū)域內(nèi)存回收的目的主要針對(duì)常量池的回收和類的卸載。
5.運(yùn)行時(shí)常量池(Runtime Constant Pool)
方法區(qū)內(nèi)部有一個(gè)非常重要的區(qū)域疾层,叫做運(yùn)行時(shí)常量池(Runtime Constant Pool将饺,簡(jiǎn)稱 RCP)。在字節(jié)碼文件(Class文件)中痛黎,除了有類的版本俯逾、字段、方法舅逸、接口等先關(guān)信息描述外,還有常量池(Constant Pool Table)信息皇筛,用于存儲(chǔ)編譯器產(chǎn)生的字面量和符號(hào)引用琉历。這部分內(nèi)容在類被加載后,都會(huì)存儲(chǔ)到方法區(qū)中的RCP水醋。值得注意的是旗笔,運(yùn)行時(shí)產(chǎn)生的新常量也可以被放入常量池中,比如 String 類中的 intern() 方法產(chǎn)生的常量拄踪。
常量池就是這個(gè)類型用到的常量的一個(gè)有序集合蝇恶。包括直接常量(基本類型,String)和對(duì)其他類型惶桐、方法撮弧、字段的符號(hào)引用.例如:
◆類和接口的全限定名;
◆字段的名稱和描述符姚糊;
◆方法和名稱和描述符贿衍。
池中的數(shù)據(jù)和數(shù)組一樣通過(guò)索引訪問(wèn)。由于常量池包含了一個(gè)類型所有的對(duì)其他類型救恨、方法贸辈、字段的符號(hào)引用,所以常量池在Java的動(dòng)態(tài)鏈接中起了核心作用
6.Native Method Stack
與VM Strack相似肠槽,VM Strack為JVM提供執(zhí)行JAVA方法的服務(wù)擎淤,Native Method Stack則為JVM提供使用native 方法的服務(wù)奢啥。
7.直接內(nèi)存區(qū)
直接內(nèi)存區(qū)并不是 JVM 管理的內(nèi)存區(qū)域的一部分,而是其之外的嘴拢。該區(qū)域也會(huì)在 Java 開(kāi)發(fā)中使用到桩盲,并且存在導(dǎo)致內(nèi)存溢出的隱患。如果你對(duì) NIO 有所了解炊汤,可能會(huì)知道 NIO 是可以使用 Native Methods 來(lái)使用直接內(nèi)存區(qū)的正驻。
小結(jié):
在此,你對(duì)JVM的內(nèi)存區(qū)域有了一定的理解抢腐,JVM內(nèi)存區(qū)域可以分為線程共享和非線程共享兩部分姑曙,線程共享的有堆和方法區(qū),非線程共享的有虛擬機(jī)棧迈倍,本地方法棧和程序計(jì)數(shù)器伤靠。