JAVA內(nèi)存區(qū)域劃分

由于Java程序是交由JVM執(zhí)行的,所以我們在談Java內(nèi)存區(qū)域劃分的時候事實(shí)上是指JVM內(nèi)存區(qū)域劃分郑口。在討論JVM內(nèi)存區(qū)域劃分之前鸳碧,先來看一下Java程序具體執(zhí)行的過程:


這里虛擬機(jī)的其中一個作用是將字節(jié)碼轉(zhuǎn)為機(jī)器碼,以此來實(shí)現(xiàn)JAVA的跨平臺作用犬性。但這并不是現(xiàn)在的重點(diǎn)瞻离。

在整個程序執(zhí)行過程中,JVM會用一段空間來存儲程序執(zhí)行期間需要用到的數(shù)據(jù)和相關(guān)信息乒裆,這段空間一般被稱作為Runtime Data Area(運(yùn)行時數(shù)據(jù)區(qū))套利,也就是我們常說的JVM內(nèi)存。因此鹤耍,在Java中我們常常說到的內(nèi)存管理就是針對這段空間進(jìn)行管理(如何分配和回收內(nèi)存空間)肉迫。

運(yùn)行時數(shù)據(jù)區(qū)包括哪幾部分?


 程序計數(shù)器(Program Counter Register)

? 也有稱作為PC寄存器稿黄。想必學(xué)過匯編語言的朋友對程序計數(shù)器這個概念并不陌生喊衫,在匯編語言中,程序計數(shù)器是指CPU中的寄存器杆怕,它保存的是程序當(dāng)前執(zhí)行的指令的地址(也可以說保存下一條指令的所在存儲單元的地址)族购,當(dāng)CPU需要執(zhí)行指令時,需要從程序計數(shù)器中得到當(dāng)前需要執(zhí)行的指令所在存儲單元的地址陵珍,然后根據(jù)得到的地址獲取到指令寝杖,在得到指令之后,程序計數(shù)器便自動加1或者根據(jù)轉(zhuǎn)移指針得到下一條指令的地址互纯,如此循環(huán)瑟幕,直至執(zhí)行完所有的指令。

雖然JVM中的程序計數(shù)器并不像匯編語言中的程序計數(shù)器一樣是物理概念上的CPU寄存器伟姐,但是JVM中的程序計數(shù)器的功能跟匯編語言中的程序計數(shù)器的功能在邏輯上是等同的收苏,也就是說是用來指示 執(zhí)行哪條指令的。

 由于在JVM中愤兵,多線程是通過線程輪流切換來獲得CPU執(zhí)行時間的鹿霸,因此,在任一具體時刻秆乳,一個CPU的內(nèi)核只會執(zhí)行一條線程中的指令懦鼠,因此,為了能夠使得每個線程都在線程切換后能夠恢復(fù)在切換之前的程序執(zhí)行位置屹堰,每個線程都需要有自己獨(dú)立的程序計數(shù)器肛冶,并且不能互相被干擾,否則就會影響到程序的正常執(zhí)行次序扯键。因此睦袖,可以這么說,程序計數(shù)器是每個線程所私有的荣刑。

? 程序計數(shù)器可以簡單理解為一個代碼行數(shù)指示器馅笙,表示當(dāng)前機(jī)器已經(jīng)讀到了第幾行代碼伦乔。


Java棧

? ? ? Java棧也稱作虛擬機(jī)棧(Java Vitual Machine Stack),也就是我們常常所說的棧董习,跟C語言的數(shù)據(jù)段中的棧類似烈和。事實(shí)上,Java棧是Java方法執(zhí)行的內(nèi)存模型皿淋。為什么這么說呢招刹?下面就來解釋一下其中的原因。

  Java棧中存放的是一個個的棧幀窝趣,每個棧幀對應(yīng)一個被調(diào)用的方法疯暑,在棧幀中包括局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)缰儿、指向當(dāng)前方法所屬的類的運(yùn)行時常量池(運(yùn)行時常量池的概念在方法區(qū)部分會談到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息散址。當(dāng)線程執(zhí)行一個方法時乖阵,就會隨之創(chuàng)建一個對應(yīng)的棧幀,并將建立的棧幀壓棧预麸。當(dāng)方法執(zhí)行完畢之后瞪浸,便會將棧幀出棧。因此可知吏祸,線程當(dāng)前執(zhí)行的方法所對應(yīng)的棧幀必定位于Java棧的頂部对蒲。講到這里,大家就應(yīng)該會明白為什么 在 使用 遞歸方法的時候容易導(dǎo)致棧內(nèi)存溢出的現(xiàn)象了以及為什么棧區(qū)的空間不用程序員去管理了(當(dāng)然在Java中贡翘,程序員基本不用關(guān)系到內(nèi)存分配和釋放的事情蹈矮,因?yàn)镴ava有自己的垃圾回收機(jī)制),這部分空間的分配和釋放都是由系統(tǒng)自動實(shí)施的鸣驱。對于所有的程序設(shè)計語言來說泛鸟,棧這部分空間對程序員來說是不透明的。下圖表示了一個Java棧的模型:


簡單來講踊东,執(zhí)行JAVA方法開始時北滥,將方法壓棧,若方法中調(diào)用了其它方法闸翅,則繼續(xù)將新方法壓棧再芋,當(dāng)方法全部執(zhí)行完,出棧坚冀。

這就解釋了為什么在用遞歸時會常常出現(xiàn)stackoverflow的異常济赎。因?yàn)镴AVA棧內(nèi)存也是有限的。

局部變量表,顧名思義司训,想必不用解釋大家應(yīng)該明白它的作用了吧华蜒。就是用來存儲方法中的局部變量(包括在方法中聲明的非靜態(tài)變量以及函數(shù)形參)。對于基本數(shù)據(jù)類型的變量豁遭,則直接存儲它的值,對于引用類型的變量贺拣,則存的是指向?qū)ο蟮囊帽托弧>植孔兞勘淼拇笮≡诰幾g器就可以確定其大小了,因此在程序執(zhí)行期間局部變量表的大小是不會改變的譬涡。

操作數(shù)棧闪幽,想必學(xué)過數(shù)據(jù)結(jié)構(gòu)中的棧的朋友想必對表達(dá)式求值問題不會陌生,棧最典型的一個應(yīng)用就是用來對表達(dá)式求值涡匀。想想一個線程執(zhí)行方法的過程中盯腌,實(shí)際上就是不斷執(zhí)行語句的過程,而歸根到底就是進(jìn)行計算的過程陨瘩。因此可以這么說腕够,程序中的所有計算過程都是在借助于操作數(shù)棧來完成的。

指向運(yùn)行時常量池的引用舌劳,因?yàn)樵诜椒▓?zhí)行的過程中有可能需要用到類中的常量帚湘,所以必須要有一個引用指向運(yùn)行時常量。

方法返回地址甚淡,當(dāng)一個方法執(zhí)行完畢之后,要返回之前調(diào)用它的地方贯卦,因此在棧幀中必須保存一個方法返回地址资柔。

?由于每個線程正在執(zhí)行的方法可能不同,因此每個線程都會有一個自己的Java棧撵割,互不干擾贿堰。

本地方法棧

本地方法棧與Java棧的作用和原理非常相似。區(qū)別只不過是Java棧是為執(zhí)行Java方法服務(wù)的睁枕,而本地方法棧則是為執(zhí)行本地方法(Native Method)服務(wù)的官边。在JVM規(guī)范中,并沒有對本地方發(fā)展的具體實(shí)現(xiàn)方法以及數(shù)據(jù)結(jié)構(gòu)作強(qiáng)制規(guī)定外遇,虛擬機(jī)可以自由實(shí)現(xiàn)它注簿。在HotSopt虛擬機(jī)中直接就把本地方法棧和Java棧合二為一。

  在C語言中跳仿,堆這部分空間是唯一一個程序員可以管理的內(nèi)存區(qū)域诡渴。程序員可以通過malloc函數(shù)和free函數(shù)在堆上申請和釋放空間。那么在Java中是怎么樣的呢?

  Java中的堆是用來存儲對象本身的以及數(shù)組(當(dāng)然妄辩,數(shù)組引用是存放在Java棧中的)惑灵。只不過和C語言中的不同,在Java中眼耀,程序員基本不用去關(guān)心空間釋放的問題英支,Java的垃圾回收機(jī)制會自動進(jìn)行處理。因此這部分空間也是Java垃圾收集器管理的主要區(qū)域哮伟。另外干花,堆是被所有線程共享的,在JVM中只有一個堆楞黄。

方法區(qū)

方法區(qū)在JVM中也是一個非常重要的區(qū)域池凄,它與堆一樣,是被線程共享的區(qū)域鬼廓。在方法區(qū)中肿仑,存儲了每個類的信息(包括類的名稱、方法信息碎税、字段信息)尤慰、靜態(tài)變量、常量以及編譯器編譯后的代碼等蚣录。

  在Class文件中除了類的字段割择、方法、接口等描述信息外萎河,還有一項信息是常量池荔泳,用來存儲編譯期間生成的字面量和符號引用。

 在方法區(qū)中有一個非常重要的部分就是運(yùn)行時常量池虐杯,它是每一個類或接口的常量池的運(yùn)行時表示形式玛歌,在類和接口被加載到JVM后,對應(yīng)的運(yùn)行時常量池就被創(chuàng)建出來擎椰。當(dāng)然并非Class文件常量池中的內(nèi)容才能進(jìn)入運(yùn)行時常量池支子,在運(yùn)行期間也可將新的常量放入運(yùn)行時常量池中,比如String的intern方法达舒。

在JVM規(guī)范中值朋,沒有強(qiáng)制要求方法區(qū)必須實(shí)現(xiàn)垃圾回收。很多人習(xí)慣將方法區(qū)稱為“永久代”巩搏,是因?yàn)镠otSpot虛擬機(jī)以永久代來實(shí)現(xiàn)方法區(qū)昨登,從而JVM的垃圾收集器可以像管理堆區(qū)一樣管理這部分區(qū)域,從而不需要專門為這部分設(shè)計垃圾回收機(jī)制贯底。不過自從JDK7之后丰辣,Hotspot虛擬機(jī)便將運(yùn)行時常量池從永久代移除了。

基于上述的說明, 可以很容易的總結(jié)出堆棧內(nèi)存的以下差異

1, 堆內(nèi)存屬于java 應(yīng)用程序所使用, 棧內(nèi)存屬于線程所私有的, 它的生命周期與線程相同?

2, 不論何時創(chuàng)建一個對象, 它總是存儲在堆內(nèi)存空間 并且棧內(nèi)存空間包含對它的引用 . 棧內(nèi)存空間只包含方法原始數(shù)據(jù)類型局部變量以及堆空間中對象的引用變量?

3, 在堆中的對象可以全局訪問, 棧內(nèi)存空間屬于線程所私有?

4, jvm 棧內(nèi)存結(jié)構(gòu)管理較為簡單, 遵循LIFO 的原則, 堆空間內(nèi)存管理較為復(fù)雜 , 細(xì)分為:新生代和老年代 etc..?

5, 棧內(nèi)存生命周期短暫, 而堆內(nèi)存伴隨整個用用程序的生命周期?

6, 二者拋出異常的方式, 如果線程請求的棧深度大于虛擬機(jī)所允許的深度笙什,將拋出StackOverflowError異常, 堆內(nèi)存拋出OutOfMemoryError異常

下面以例子來說明一下:


可以看到飘哨,MAIN方法中,NEW了兩個對象琐凭,調(diào)用了一個方法:


可以看出芽隆,方法里的基本變量,是存在棧區(qū)统屈,方法中的對象摆马,在棧區(qū)只是有引用,真正的對象是在堆區(qū)鸿吆。

方法中的String基本型,是在常量池中述呐。但如果是 String a = new String("abc");? 那此時的a是對象惩淳! 就不會在常量池中,而在堆區(qū)乓搬!??


我們來看看執(zhí)行程序的步驟思犁。

一旦我們開始運(yùn)行程序, 它會把所有的運(yùn)行時類加載到堆內(nèi)存空間, 在 Line 1 行找到main() 方法进肯, Java Runtime 創(chuàng)建由main() 方法線程使用的棧內(nèi)存空間

在第二行 我們創(chuàng)建了原始數(shù)據(jù)類型的局部變量, 所以它將被存儲在main() 方法的棧內(nèi)存空間

在第3行我們創(chuàng)建了一個Object 類型的對象, 所以它被創(chuàng)建在Heap 堆內(nèi)存空間中 并且 Stack 棧內(nèi)存空間包含對它的引用, 當(dāng)我們在第4行中創(chuàng)建Memory 對象時, 會發(fā)生類似的過程

現(xiàn)在我們在第5行調(diào)用foo() 方法, 此時會在stack 棧創(chuàng)建一個block 供foo() 方法使用

Java 是通過值傳遞, 在第6行, 會在foo() 棧中創(chuàng)建一個對Object 對象的新的引用

在第7行 , 一個string 類型的對象被創(chuàng)建, 此時 會在foo() 棧內(nèi)存中創(chuàng)建它的一個引用 str

foo() 方法在第8行執(zhí)行完畢, 此時, 程序會釋放stack 棧內(nèi)存中為foo() 方法分配的棧內(nèi)存空間

在第9行, main() 方法執(zhí)行完畢激蹲, 為main()方法創(chuàng)建的堆棧內(nèi)存被銷毀, 此時 這個java 程序結(jié)束運(yùn)行, Java Runtime 會釋放所有的內(nèi)存

現(xiàn)在就可以講講JAVA指針的含義:實(shí)際上,棧中的變量指向堆內(nèi)存中的變量江掩,這就是Java中的指針学辱!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市环形,隨后出現(xiàn)的幾起案子策泣,更是在濱河造成了極大的恐慌,老刑警劉巖抬吟,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萨咕,死亡現(xiàn)場離奇詭異,居然都是意外死亡火本,警方通過查閱死者的電腦和手機(jī)危队,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钙畔,“玉大人茫陆,你說我怎么就攤上這事∪婿” “怎么了盅弛?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我挪鹏,道長见秽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任讨盒,我火速辦了婚禮解取,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘返顺。我一直安慰自己禀苦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布遂鹊。 她就那樣靜靜地躺著振乏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秉扑。 梳的紋絲不亂的頭發(fā)上慧邮,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音舟陆,去河邊找鬼误澳。 笑死,一個胖子當(dāng)著我的面吹牛秦躯,可吹牛的內(nèi)容都是我干的忆谓。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼踱承,長吁一口氣:“原來是場噩夢啊……” “哼倡缠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茎活,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤毡琉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妙色,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桅滋,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年身辨,在試婚紗的時候發(fā)現(xiàn)自己被綠了丐谋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡煌珊,死狀恐怖号俐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情定庵,我是刑警寧澤吏饿,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布踪危,位于F島的核電站,受9級特大地震影響猪落,放射性物質(zhì)發(fā)生泄漏贞远。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一笨忌、第九天 我趴在偏房一處隱蔽的房頂上張望蓝仲。 院中可真熱鬧,春花似錦官疲、人聲如沸袱结。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垢夹。三九已至,卻和暖如春维费,著一層夾襖步出監(jiān)牢的瞬間棚饵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工掩完, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人硼砰。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓且蓬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親题翰。 傳聞我的和親對象是個殘疾皇子恶阴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容