JVM內(nèi)存分配:堆、棧和方法區(qū)
摘要:基本類型的變量、對象的引用和函數(shù)調(diào)用的現(xiàn)場等存儲(chǔ)在棧中,通過new關(guān)鍵字和構(gòu)造器創(chuàng)建的對象存儲(chǔ)在堆中,字面量如100、”hello”和常量等存儲(chǔ)在靜態(tài)區(qū)。
概述
??我們首先介紹一個(gè)本文要經(jīng)常提及的術(shù)語字面量(literal)。在計(jì)算機(jī)科學(xué)中,字面量是用于表達(dá)源代碼中一個(gè)固定值的表示法(notation)。幾乎所有計(jì)算機(jī)編程語言都具有對基本類型變量值的字面量表示,諸如整數(shù)、浮點(diǎn)數(shù)以及字符串,其中常見的字符串字面量是指雙引號引起來的一系列字符,雙引號中可以沒有字符,可以只有一個(gè)字符,也可以有很多字符。
int a = 100; // 100為int類型字面量
String bStr = "Hello world" // Hello world 為字符串字面量
??字面量作為一種通用的,跨平臺(tái)的數(shù)據(jù)交換格式,在程序界是公認(rèn)的事實(shí)。
??言歸正傳,下面聊聊本文的主角。JVM中內(nèi)存分配一共有三個(gè)區(qū):堆區(qū)(heap)、棧區(qū)(stack)和方法區(qū)(靜態(tài)區(qū)),了解java的這3大區(qū)域非常有必要,尤其是工作中需要對jvm性能調(diào)優(yōu),更應(yīng)該深度掌握下它們的概念和作用。
堆區(qū)
??堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),專門用來保存類(class)的實(shí)例(instance)并分配空間,不存放基本類型和對象引用,例如new 創(chuàng)建的實(shí)例和數(shù)組,實(shí)際上只是保存實(shí)例的字面量、類型和類型標(biāo)記等,然而并不保存實(shí)例的方法(方法是指令,保存在下一節(jié)介紹的棧中)。
??jvm只有一個(gè)堆區(qū),它被所有線程共享。堆的大小是由垃圾收集器來負(fù)責(zé)的,優(yōu)勢是可以動(dòng)態(tài)地分配內(nèi)存大小,生命周期也不必事先告訴編譯器,因?yàn)樗窃谶\(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存的,垃圾收集器會(huì)自動(dòng)收走不再使用的數(shù)據(jù)。缺點(diǎn)是由于要在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存,所以,存取速度較慢。
棧區(qū)
??每個(gè)線程包含一個(gè)棧區(qū),棧中只保存基礎(chǔ)數(shù)據(jù)類型變量的字面量和自定義對象的引用(不是對象),對象都存放在堆區(qū)中。
??每個(gè)棧中的數(shù)據(jù)(基本類型和對象引用)都是私有的,其它棧不能訪問。棧分為3個(gè)部分:基本類型變量區(qū)、執(zhí)行環(huán)境上下文、操作指令區(qū)(存放操作指令)。
方法區(qū)
??方法區(qū)又叫靜態(tài)區(qū),跟堆一樣,被所有的線程共享。它存儲(chǔ)的都是在整個(gè)程序中永遠(yuǎn)唯一的元素,如class和static變量。全局變量和靜態(tài)成員變量的存儲(chǔ)是放在一塊的,但是,初始化和未初始化的分別存儲(chǔ)在相鄰的兩塊區(qū)域。
??虛擬機(jī)的體系結(jié)構(gòu)包括堆、方法區(qū)、本地方法棧和pc寄存器。而方法區(qū)保存的就是一個(gè)類的模板,堆是放類的實(shí)例的,一般來用于函數(shù)計(jì)算,它里面的數(shù)據(jù)在函數(shù)執(zhí)行完時(shí)是不會(huì)存儲(chǔ)的,直接丟棄。這就是為什么局部變量每一次都是一樣的,即便修改了它后,下次執(zhí)行函數(shù)的時(shí)候還是原來的值。
??如果棧內(nèi)存或者堆內(nèi)存不足都會(huì)拋出異常:
-
棧空間不足:java.lang.StackOverFlowError。
-
堆空間不足:java.lang.OutOfMemoryError。
??棧的空間大小遠(yuǎn)遠(yuǎn)小于堆的。棧空間操作起來最快但是空間很小,通常大量的對象都是放在堆空間,理論上整個(gè)內(nèi)存沒有被其它進(jìn)程使用的空間甚至硬盤上的虛擬內(nèi)存都可以被當(dāng)成堆空間來使用。
為什么要把堆和棧區(qū)分出來呢?棧中不是也可以存儲(chǔ)數(shù)據(jù)嗎?
??第一,從軟件設(shè)計(jì)的角度看,棧代表了處理邏輯,而堆代表了數(shù)據(jù)。這樣分開,使得處理邏輯更為清晰,體現(xiàn)了分而治之的思想,這種隔離、模塊化的思想在軟件設(shè)計(jì)的方方面面都有體現(xiàn)。
??第二,堆與棧的分離,使得堆中的內(nèi)容可以被多個(gè)棧共享(也可以理解為多個(gè)線程訪問同一個(gè)對象)。這種共享有很多益處。一方面,它提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存),另一方面,堆中的共享常量和緩存可以被所有棧訪問,節(jié)約空間。
??第三,棧因?yàn)檫\(yùn)行時(shí)的需要,比如保存系統(tǒng)運(yùn)行的上下文,需要進(jìn)行地址段的劃分。由于棧只能向上增長,因此就會(huì)限制棧存儲(chǔ)內(nèi)容的能力。而堆不同,堆中的對象可以根據(jù)需要?jiǎng)討B(tài)調(diào)整,因此棧和堆的拆分,使得動(dòng)態(tài)增長成為可能,相應(yīng)棧中只需記錄堆中的一個(gè)地址即可。
??第四,面向?qū)ο缶褪嵌押蜅5耐昝澜Y(jié)合。其實(shí),面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒有任何區(qū)別。但是,面向?qū)ο蟮囊耄沟脤Υ龁栴}的思考方式發(fā)生了改變,而更接近于自然方式的思考。當(dāng)我們把對象拆開時(shí)就會(huì)發(fā)現(xiàn)對象的屬性其實(shí)就是數(shù)據(jù),存放在堆中;而對象的行為(方法),就是算法(運(yùn)行邏輯),放在棧中。我們在編寫對象的時(shí)候,其實(shí)即編寫了數(shù)據(jù)結(jié)構(gòu),也編寫的處理數(shù)據(jù)的算法。不得不承認(rèn),面向?qū)ο蟮脑O(shè)計(jì)確實(shí)極具曲線美。
知行合一
??下面分析一下基礎(chǔ)類型變量內(nèi)存分配過程。
int a = 3;
int b = 3;
??編譯器處理int a = 3時(shí),首先,它會(huì)在棧中創(chuàng)建一個(gè)變量為a的引用;然后,查找棧中是否有3這個(gè)字面量,如果沒找到,就將3存放進(jìn)來,然后將a指向3。接著處理int b = 3,在創(chuàng)建完b的引用變量后,因?yàn)樵跅V幸呀?jīng)有字面量3,便將b直接指向3。這樣,就出現(xiàn)了a與b均指向3的情況。
??這時(shí),如果再令a=4,那么編譯器會(huì)重新搜索棧中是否有字面量4,如若沒有,則將4存放進(jìn)來,并令a重新指向4;如果已經(jīng)有了,則直接將a重新指向它。故改變a值的時(shí)候不會(huì)影響到b的值。要注意棧中字面量的共享與兩個(gè)對象的引用同時(shí)指向一個(gè)堆中對象的共享是不同的,因?yàn)榛A(chǔ)類型變量a的修改并不會(huì)影響到b,它是由編譯器完成的,目標(biāo)是節(jié)省空間;而一個(gè)對象引用變量修改了堆中對象的內(nèi)部狀態(tài)時(shí),會(huì)影響其它共同引用了堆中對象的變量。
String bStr = "Hello world";
??上面的語句中變量bStr放在棧上,用new創(chuàng)建出來的字符串對象放在堆上,而“Hello world”這個(gè)字面量放在靜態(tài)區(qū)。
Reference
Buy me a coffee. ?Get red packets.
浙公網(wǎng)安備 33010602011771號