Java中運(yùn)行時(shí)內(nèi)存結(jié)構(gòu)
<wbr><wbr></wbr></wbr> 1.1 方法區(qū): <wbr></wbr>
方法區(qū)是系統(tǒng)分配的一個(gè)內(nèi)存邏輯區(qū)域,是 JVM 在裝載類(lèi)文件時(shí),用于存儲(chǔ)類(lèi)型信息的 ( 類(lèi)的描述信息 )。
<wbr></wbr>
方法區(qū)存放的信息包括:
<wbr><wbr><wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr> 1.1.1 類(lèi)的基本信息:
- 每個(gè)類(lèi)的全限定名
- 每個(gè)類(lèi)的直接超類(lèi)的全限定名 ( 可約束類(lèi)型轉(zhuǎn)換 )
- 該類(lèi)是類(lèi)還是接口
- 該類(lèi)型的訪問(wèn)修飾符
- 直接超接口的全限定名的有序列表
<wbr><wbr><wbr><wbr><wbr><wbr><wbr>1.1.2<strong>已裝載類(lèi)的詳細(xì)信息</strong>:</wbr></wbr></wbr></wbr></wbr></wbr></wbr>
-
<wbr><span style="word-wrap:normal; word-break:normal; color:rgb(255,0,0)">運(yùn)行時(shí)常量池</span>:</wbr>
在方法區(qū)中,每個(gè)類(lèi)型都對(duì)應(yīng)一個(gè)常量池,存放該類(lèi)型所用到的所有常量,常量池中存儲(chǔ)了諸如 文字字符串、 final 變量值、類(lèi)名和方法名常量。 它們以數(shù)組形式通過(guò)索引被訪問(wèn),是外部調(diào)用與類(lèi)聯(lián)系及類(lèi)型對(duì)象化的橋梁。(存的可能是個(gè)普通的字符串,然后經(jīng)過(guò)常量池解析,則變成指向某個(gè)類(lèi)的引用)
-
<wbr><span style="word-wrap:normal; word-break:normal; color:rgb(255,0,0)">字段信息</span>:</wbr>
字段信息存放類(lèi)中聲明的每一個(gè)字段的信息,包括字段的名、類(lèi)型、修飾符。
字段名稱(chēng)指的是類(lèi)或接口的實(shí)例變量或類(lèi)變量,字段的描述符是一個(gè)指示字段的類(lèi)型的字符串,如 private A a=null; 則 a 為字段名, A 為描述符, private 為修飾符
-
<wbr><span style="word-wrap:normal; word-break:normal; color:rgb(255,0,0)">方法信息</span>:</wbr>
類(lèi)中聲明的每一個(gè)方法的信息,包括方法名、返回值類(lèi)型、參數(shù)類(lèi)型、修飾符、異常、方法的字節(jié)碼。
(在編譯的時(shí)候,就已經(jīng)將方法的局部變量、操作數(shù)棧大小等確定并存放在字節(jié)碼中,在裝載的時(shí)候,隨著類(lèi)一起裝入方法區(qū) 。)
在運(yùn)行時(shí),JVM從常量池中獲得符號(hào)引用,然后在運(yùn)行時(shí)解析成引用項(xiàng)的實(shí)際地址,最后通過(guò)常量池中的全限定名、方法和字段描述符,把當(dāng)前類(lèi)或接口中的代碼與其它類(lèi)或接口中的代碼聯(lián)系起來(lái)。 -
<wbr><span style="word-wrap:normal; word-break:normal; color:rgb(255,0,0)">靜態(tài)變量</span>:</wbr>
這個(gè)沒(méi)什么好說(shuō)的,就是類(lèi)變量,類(lèi)的所有實(shí)例都共享,我們只需知道,在方法區(qū)有個(gè)靜態(tài)區(qū),靜態(tài)區(qū)專(zhuān)門(mén)存放靜態(tài)變量和靜態(tài)塊。
- <wbr></wbr> 到類(lèi) classloader 的引用 : 到該類(lèi)的類(lèi)裝載器的引用。
- <wbr></wbr> 到類(lèi) class 的引用 : 虛擬機(jī)為每一個(gè)被裝載的類(lèi)型創(chuàng)建一個(gè) class 實(shí)例,用來(lái)代表這個(gè)被裝載的類(lèi)。 <wbr></wbr>
<wbr></wbr> <wbr></wbr> 由此我們可以知道反射的基礎(chǔ) :
在裝載類(lèi)的時(shí)候,加入方法區(qū)中的所有信息,最后都會(huì)形成Class類(lèi)的實(shí)例,代表這個(gè)被裝載的類(lèi)。方法區(qū)中的所有的信息,都是可以通過(guò)這個(gè)Class類(lèi)對(duì)象反射得
到。
我們知道對(duì)象是類(lèi)的實(shí)例,類(lèi)是相同結(jié)構(gòu)的對(duì)象的一種抽象。同類(lèi)的各個(gè)對(duì)象之間,其實(shí)是擁有相同的結(jié)構(gòu)(屬性),擁有相同的功能(方法),
各個(gè)對(duì)象的區(qū)別只在于屬性值的不同
。
<wbr><wbr>同樣的,我們所有的類(lèi),其實(shí)都是Class類(lèi)的實(shí)例,他們都擁有相同的結(jié)構(gòu)-----Field數(shù)組、Method數(shù)組。而各個(gè)類(lèi)中的屬性都是Field屬性的一個(gè)具體屬性值,方法都是Method屬性的一個(gè)具體屬性值。</wbr></wbr> |
<wbr></wbr>
<wbr><span style="word-wrap:normal; word-break:normal; line-height:24px; font-size:14px"><span style="word-wrap:normal; word-break:normal; color:rgb(0,204,255)"><span style="color:#000000; word-wrap:normal; word-break:normal"><strong>在運(yùn)行時(shí),JVM從常量池中獲得符號(hào)引用,然后在運(yùn)行時(shí)解析成引用項(xiàng)的實(shí)際地址,最后通過(guò)常量池中的全限定名、方法和字段描述符,把當(dāng)前類(lèi)或接口中的代碼與其它類(lèi)或接口中的代碼聯(lián)系起來(lái)。</strong></span></span></span></wbr>
<wbr></wbr>
1.2 Java棧
JVM 棧是程序運(yùn)行時(shí)單位,決定了程序如何執(zhí)行,或者說(shuō)數(shù)據(jù)如何處理。
在 Java 中,一個(gè)線程就會(huì)有一個(gè)線程的 JVM 棧與之對(duì)應(yīng),因?yàn)椴贿^(guò)的線程執(zhí)行邏輯顯然不同,因此都需要一個(gè)獨(dú)立的 JVM 棧來(lái)存放該線程的執(zhí)行邏輯。
對(duì)方法的調(diào)用:
Java 棧內(nèi)存,以 幀 的形式存放 本地方法 的 調(diào)用狀態(tài) ,包括方法調(diào)用的 參數(shù) 、 局部變量、中間結(jié)果 等(方法都是以方法幀的形式存放在方法區(qū)的),每調(diào)用一個(gè)方法就將對(duì)應(yīng)該方法的方法幀壓入 Java 棧,成為當(dāng)前方法幀。當(dāng)調(diào)用結(jié)束 ( 返回 ) 時(shí),就彈出該幀。
<wbr></wbr>
這意味著:
在方法中定義的一些 基本類(lèi)型 的變量和 引用變量 都在方法的棧內(nèi)存中分配。 當(dāng)在一段代碼塊定義一個(gè)變量時(shí), Java 就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過(guò)變量的作用域后(方法執(zhí)行完成后), Java 會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間,該內(nèi)存空間可以立即被另作它用 。 -------- 同時(shí),因?yàn)樽兞勘会尫牛撟兞繉?duì)應(yīng)的對(duì)象,也就失去了引用,也就變成了可以被gc對(duì)象回收的垃圾。
因此我們可以知道成員變量與局部變量的區(qū)別:
<wbr></wbr>
局部變量,在方法內(nèi)部聲明,當(dāng)該方法運(yùn)行完時(shí),內(nèi)存即被釋放。
成員變量,只要該對(duì)象還在,哪怕某一個(gè)方法運(yùn)行完了,還是存在。 從系統(tǒng)的角度來(lái)說(shuō),聲明局部變量有利于內(nèi)存空間的更高效利用(方法運(yùn)行完即回收)。 成員變量可用于各個(gè)方法間進(jìn)行數(shù)據(jù)共享。 |
<wbr></wbr>
Java 棧內(nèi)存的組成:
局部變量區(qū)、操作數(shù)棧、幀數(shù)據(jù)區(qū)組成。
(1):局部變量區(qū)為一個(gè)以字為單位的數(shù)組,每個(gè)數(shù)組元素對(duì)應(yīng)一個(gè)局部變量的值。調(diào)用方法時(shí),將方法的局部變量組成一個(gè)數(shù)組,通過(guò)索引來(lái)訪問(wèn)。若為非靜態(tài)方法,則加入一個(gè)隱含的引用參數(shù)this,該參數(shù)指向調(diào)用這個(gè)方法的對(duì)象。而靜態(tài)方法則沒(méi)有this參數(shù)。因此,對(duì)象無(wú)法調(diào)用靜態(tài)方法。
<wbr></wbr>
由此,我們可以知道,方法什么時(shí)候設(shè)計(jì)為靜態(tài),什么時(shí)候?yàn)榉庆o態(tài)?
前面已經(jīng)說(shuō)過(guò),對(duì)象是類(lèi)的一個(gè)實(shí)例,各個(gè)對(duì)象結(jié)構(gòu)相同,只是屬性不同。
而靜態(tài)方法是對(duì)象無(wú)法調(diào)用的。 所以,靜態(tài)方法適合那些工具類(lèi)中的工具方法,這些類(lèi)只是用來(lái)實(shí)現(xiàn)一些功能,也不需要產(chǎn)生對(duì)象,通過(guò)設(shè)置對(duì)象的屬性來(lái)得到各個(gè)不同的個(gè)體。 |
(2):操作數(shù)棧也是一個(gè)數(shù)組,但是通過(guò)棧操作來(lái)訪問(wèn)。所謂操作數(shù)是那些被指令操作的數(shù)據(jù)。當(dāng)需要對(duì)參數(shù)操作時(shí)如a=b+c,就將即將被操作的參數(shù)壓棧,如將b 和c 壓棧,然后由操作指令將它們彈出,并執(zhí)行操作。虛擬機(jī)將操作數(shù)棧作為工作區(qū)。
(3):幀數(shù)據(jù)區(qū)處理常量池解析,異常處理等
<wbr></wbr>
1.3 java堆<wbr></wbr>
<wbr><wbr><wbr>java的堆是一個(gè)運(yùn)行時(shí)的數(shù)據(jù)區(qū),用來(lái)存儲(chǔ)數(shù)據(jù)的單元,存放通過(guò)new關(guān)鍵字新建的</wbr></wbr></wbr>
對(duì)象
和
數(shù)組
,對(duì)象從中分配內(nèi)存。
<wbr><wbr><wbr>在堆中聲明的對(duì)象,是不能直接訪問(wèn)的,必須通過(guò)在棧中聲明的指向該引用的變量來(lái)調(diào)用。引用變量就相當(dāng)于是為數(shù)組或?qū)ο笃鸬囊粋€(gè)名稱(chēng),以后就可以在程序中使用棧中的引用變量來(lái)訪問(wèn)堆中的數(shù)組或?qū)ο蟆?lt;/wbr></wbr></wbr>
<wbr><wbr></wbr></wbr>
<wbr><wbr></wbr></wbr> <wbr>由此我們可以知道,引用類(lèi)型變量和對(duì)象的區(qū)別:<wbr></wbr></wbr>
聲明的對(duì)象是在堆內(nèi)存中初始化的,真正用來(lái)存儲(chǔ)數(shù)據(jù)的。不能直接訪問(wèn)。
引用類(lèi)型變量是保存在棧當(dāng)中的,一個(gè)用來(lái)引用堆中對(duì)象的符號(hào)而已(指針)。 |
堆與棧的比較
:
JAVA堆與棧都是用來(lái)存放數(shù)據(jù)的,那么他們之間到底有什么差異呢?既然棧也能存放數(shù)據(jù),為什么還要設(shè)計(jì)堆呢?
1. 從存放數(shù)據(jù)的角度:
<wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr> 前面我們已經(jīng)說(shuō)明:
<wbr><wbr><wbr>棧中存放的是基本類(lèi)型的變量</wbr></wbr></wbr> or 引用類(lèi)型的變量
<wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr> 堆中存放的是對(duì)象 or 數(shù)組對(duì)象.
<wbr><wbr><wbr><wbr>在棧中,引用變量的大小為32位,基本類(lèi)型為1-8個(gè)字節(jié)。</wbr></wbr></wbr></wbr>
<wbr><wbr><wbr><wbr>但是對(duì)象的大小和數(shù)組的大小是動(dòng)態(tài)的,這也決定了堆中數(shù)據(jù)的動(dòng)態(tài)性,因?yàn)樗窃谶\(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存的,</wbr></wbr></wbr></wbr>
生存期也不必在編譯時(shí)確定,
Java 的垃圾收集器會(huì)自動(dòng)收走這些不再使用的數(shù)據(jù)。
<wbr></wbr>
2. 從數(shù)據(jù)共享的角度:
<wbr><wbr>1).在單個(gè)線程類(lèi),棧中的數(shù)據(jù)可共享</wbr></wbr>
<wbr><wbr>例如我們定義:</wbr></wbr>

- int <wbr>a=</wbr> 3 ; <wbr><wbr></wbr></wbr>
- int <wbr>b=</wbr> 3 ;<wbr><wbr></wbr></wbr>
<wbr><wbr></wbr></wbr> 編譯器先處理int a = 3;首先它會(huì)在棧中創(chuàng)建一個(gè)變量為a 的引用,然后查找棧中是否有3 這個(gè)值,如果沒(méi)找到,就將3 存放進(jìn)來(lái),然后將a 指向3。接著處理int b = 3;在創(chuàng)建完b 的引用變量后,因?yàn)樵跅V幸呀?jīng)有3這個(gè)值,便將b 直接指向3。這樣,就出現(xiàn)了a 與b 同時(shí)均指向3的情況。
<wbr><wbr>而如果我們定義:</wbr></wbr> <wbr></wbr>

- Integer<wbr>a=</wbr> new <wbr>Integer(</wbr> 3 ); //(1) <wbr><wbr></wbr></wbr>
- Integer<wbr>b=</wbr> new <wbr>Integer(</wbr> 3 ); //(2) <wbr><wbr></wbr></wbr>
<wbr><wbr></wbr></wbr>
這個(gè)時(shí)候執(zhí)行過(guò)程為:在執(zhí)行(1)時(shí),首先在棧中創(chuàng)建一個(gè)變量a,然后在堆內(nèi)存中實(shí)例化一個(gè)對(duì)象,并且將變量a指向這個(gè)實(shí)例化的對(duì)象。在執(zhí)行(2)時(shí),過(guò)程類(lèi)似,此時(shí),在堆內(nèi)存中,會(huì)有兩個(gè)Integer類(lèi)型的對(duì)象。<wbr></wbr>
<wbr></wbr>
<wbr><wbr>2).<span style="word-wrap:normal; word-break:normal; color:rgb(255,0,0)"><strong>在進(jìn)程的各個(gè)線程之間,數(shù)據(jù)的共享通過(guò)堆來(lái)實(shí)現(xiàn)</strong></span></wbr></wbr>
<wbr><wbr><wbr><wbr></wbr></wbr></wbr></wbr>
例:那么,在多線程開(kāi)發(fā)中,我們的數(shù)據(jù)共享又是怎么實(shí)現(xiàn)的呢?
如圖所示,堆中的數(shù)據(jù)是所有線程棧所共享的,我們可以通過(guò)參數(shù)傳遞,將一個(gè)堆中的數(shù)據(jù)傳入各個(gè)棧的工作內(nèi)存中,從而實(shí)現(xiàn)多個(gè)線程間的數(shù)據(jù)共享
(多個(gè)進(jìn)程間的數(shù)據(jù)共享則需要通過(guò)網(wǎng)絡(luò)傳輸了。)<wbr></wbr>
<wbr></wbr>
3.從程序設(shè)計(jì)的的角度:
從軟件設(shè)計(jì)的角度看,JVM棧代表了處理邏輯,而JVM堆代表了數(shù)據(jù)。這樣分開(kāi),使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設(shè)計(jì)的方方面面都有體現(xiàn)。
4.值傳遞和引用傳遞的真相
有了以上關(guān)于棧和堆的種種了解后,我們很容易就可以知道值傳遞和引用傳遞的真相:
1.程序運(yùn)行永遠(yuǎn)都是在JVM棧中進(jìn)行的,因而參數(shù)傳遞時(shí),只存在傳遞基本類(lèi)型和對(duì)象引用的問(wèn)題。不會(huì)直接傳對(duì)象本身。 但是傳引用的錯(cuò)覺(jué)是如何造成的呢? 在運(yùn)行JVM棧中,基本類(lèi)型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調(diào)用,也同時(shí)可以理解為“傳引用值”的傳值調(diào)用,即引用的處理跟基本類(lèi)型是完全一樣的。 但是當(dāng)進(jìn)入被調(diào)用方法時(shí),被傳遞的這個(gè)引用的值,被程序解釋(或者查找)到JVM堆中的對(duì)象,這個(gè)時(shí)候才對(duì)應(yīng)到真正的對(duì)象。 如果此時(shí)進(jìn)行修改,修改的是引用對(duì)應(yīng)的對(duì)象,而不是引用本身,即:修改的是JVM堆中的數(shù)據(jù)。所以這個(gè)修改是可以保持的了。 |
從某種意義上來(lái)說(shuō) 對(duì)象都 是由基本類(lèi)型組成的。 <wbr></wbr>
可以把一個(gè)對(duì)象看作為一棵樹(shù),對(duì)象的屬性如果還是對(duì)象,則還是一顆樹(shù)(即非葉子節(jié)點(diǎn)),基本類(lèi)型則為樹(shù)的葉子節(jié)點(diǎn)。程序參數(shù)傳遞時(shí),被傳遞的值本身都是不能進(jìn)行修改的,但是,如果這個(gè)值是一個(gè)非葉子節(jié)點(diǎn)(即一個(gè)對(duì)象引用),則可以修改這個(gè)節(jié)點(diǎn)下面的所有內(nèi)容。<wbr></wbr> |
其實(shí),面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒(méi)有任何區(qū)別 。
面向?qū)ο蟮囊耄皇? 改變了我們 對(duì)待問(wèn)題的思考方式,而更接近于自然方式的思考。
當(dāng)我們把對(duì)象拆開(kāi),其實(shí)對(duì)象的屬性就是數(shù)據(jù),存放在JVM堆中;而對(duì)象的行為(方法),就是運(yùn)行邏輯,放在JVM棧中。我們?cè)诰帉?xiě)對(duì)象的時(shí)候,其實(shí)即編寫(xiě)了數(shù)據(jù)結(jié)構(gòu),也編寫(xiě)的處理數(shù)據(jù)的邏輯。
原文地址 :http://blog.sina.com.cn/s/blog_67fdef9001011nzl.html
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
