http://java-mzd.iteye.com/blog/848635
?
P.S.
想寫這篇總結醞釀了有個來月了,卻始終感覺還差點什么東西,一直未敢動筆。 ? ? ? ? 最近兩天連夜奮戰, 重新整理下 前面查閱的資料、筆記,還是決定將它寫出來。 ? ? ? ? 現在提出幾個問題, 如果都能熟練回答的大蝦,請您飄過. 如以往一樣,我是小菜,本文自然也是針對小菜階層的總結。 |
?
首先是概念層面的幾個問題:
?
-
Java中運行時內存結構有哪幾種?
-
Java中為什么要設計堆棧分離?
-
Java多線程中是如何實現數據共享的?
- Java反射的基礎是什么?
?
?
?
然后是運用層面:
- 引用類型變量和對象的區別?
-
什么情況下用局部變量,什么情況下用成員變量?
-
數組如何初始化?聲明一個數組的過程中,如何分配內存?
-
聲明基本類型數組和聲明引用類型的數組,初始化時,內存分配機制有什么區?
- 在什么情況下,我們的方法設計為靜態化,為什么?(上次胡老師問文奇,問的啞口無言,當時想回答,卻老感覺表述不清楚,這里也簡單說明一下)
?
?
?
?
?
?
?
好了,問題提完了, 如果您都能一眼看出答案,那么,沒有必要再浪費您寶貴的時間看下去了 。
如果您還不太明白,請跟隨我一路走下去。
?
?
?
?
?
?
?
?
Java中運行時內存結構
? ? 1.1 方法區: ?
方法區是系統分配的一個內存邏輯區域,是 JVM 在裝載類文件時,用于存儲類型信息的 ( 類的描述信息 )。
? ??
方法區存放的信息包括:
? ? ? ? ? ? 1.1.1 類的基本信息:
- 每個類的全限定名
- 每個類的直接超類的全限定名 ( 可約束類型轉換 )
- 該類是類還是接口
- 該類型的訪問修飾符
- 直接超接口的全限定名的有序列表
?
?
?
?
?
?
?
? ? ? ? ? ? ?1.1.2 已裝載類的詳細信息 :
-
?
運行時常量池
:
在方法區中,每個類型都對應一個常量池,存放該類型所用到的所有常量,常量池中存儲了諸如 文字字符串、 final 變量值、類名和方法名常量。 它們以數組形式通過索引被訪問,是外部調用與類聯系及類型對象化的橋梁。(存的可能是個普通的字符串,然后經過常量池解析,則變成指向某個類的引用)
-
?
字段信息
:
字段信息存放類中聲明的每一個字段的信息,包括字段的名、類型、修飾符。
字段名稱指的是類或接口的實例變量或類變量,字段的描述符是一個指示字段的類型的字符串,如 private A a=null; 則 a 為字段名, A 為描述符, private 為修飾符
-
?
方法信息
:
類中聲明的每一個方法的信息,包括方法名、返回值類型、參數類型、修飾符、異常、方法的字節碼。
(在編譯的時候,就已經將方法的局部變量、操作數棧大小等確定并存放在字節碼中,在裝載的時候,隨著類一起裝入方法區 。)
在運行時,JVM從常量池中獲得符號引用,然后在運行時解析成引用項的實際地址,最后通過常量池中的全限定名、方法和字段描述符,把當前類或接口中的代碼與其它類或接口中的代碼聯系起來。 -
?
靜態變量
:
這個沒什么好說的,就是類變量,類的所有實例都共享,我們只需知道,在方法區有個靜態區,靜態區專門存放靜態變量和靜態塊。
- ? 到類 classloader 的引用 : 到該類的類裝載器的引用。
- ? 到類 class 的引用 : 虛擬機為每一個被裝載的類型創建一個 class 實例,用來代表這個被裝載的類。 ?
? ? 由此我們可以知道反射的基礎 :
在裝載類的時候,加入方法區中的所有信息,最后都會形成Class類的實例,代表這個被裝載的類。方法區中的所有的信息,都是可以通過這個Class類對象反射得
到。
我們知道對象是類的實例,類是相同結構的對象的一種抽象。同類的各個對象之間,其實是擁有相同的結構(屬性),擁有相同的功能(方法),
各個對象的區別只在于屬性值的不同
。
? ? 同樣的,我們所有的類,其實都是Class類的實例,他們都擁有相同的結構-----Field數組、Method數組。而各個類中的屬性都是Field屬性的一個具體屬性值,方法都是Method屬性的一個具體屬性值。 |
?
? 在運行時,JVM從常量池中獲得符號引用,然后在運行時解析成引用項的實際地址,最后通過常量池中的全限定名、方法和字段描述符,把當前類或接口中的代碼與其它類或接口中的代碼聯系起來。
?
?
?
?
1.2 Java棧
JVM 棧是程序運行時單位,決定了程序如何執行,或者說數據如何處理。
在 Java 中,一個線程就會有一個線程的 JVM 棧與之對應,因為不過的線程執行邏輯顯然不同,因此都需要一個獨立的 JVM 棧來存放該線程的執行邏輯。
對方法的調用:
Java 棧內存,以 幀 的形式存放 本地方法 的 調用狀態 ,包括方法調用的 參數 、 局部變量、中間結果 等(方法都是以方法幀的形式存放在方法區的),每調用一個方法就將對應該方法的方法幀壓入 Java 棧,成為當前方法幀。當調用結束 ( 返回 ) 時,就彈出該幀。
?
這意味著:
在方法中定義的一些 基本類型 的變量和 引用變量 都在方法的棧內存中分配。 當在一段代碼塊定義一個變量時, Java 就在棧中為這個變量分配內存空間,當超過變量的作用域后(方法執行完成后), Java 會自動釋放掉為該變量所分配的內存空間,該內存空間可以立即被另作它用 。 -------- 同時,因為變量被釋放,該變量對應的對象,也就失去了引用,也就變成了可以被gc對象回收的垃圾。
因此我們可以知道成員變量與局部變量的區別:
局部變量,在方法內部聲明,當該方法運行完時,內存即被釋放。
成員變量,只要該對象還在,哪怕某一個方法運行完了,還是存在。 從系統的角度來說,聲明局部變量有利于內存空間的更高效利用(方法運行完即回收)。 成員變量可用于各個方法間進行數據共享。 |
?
?
Java 棧內存的組成:
局部變量區、操作數棧、幀數據區組成。
(1):局部變量區為一個以字為單位的數組,每個數組元素對應一個局部變量的值。調用方法時,將方法的局部變量組成一個數組,通過索引來訪問。若為非靜態方法,則加入一個隱含的引用參數this,該參數指向調用這個方法的對象。而靜態方法則沒有this參數。因此,對象無法調用靜態方法。
?
由此,我們可以知道,方法什么時候設計為靜態,什么時候為非靜態?
前面已經說過,對象是類的一個實例,各個對象結構相同,只是屬性不同。
而靜態方法是對象無法調用的。 所以,靜態方法適合那些工具類中的工具方法,這些類只是用來實現一些功能,也不需要產生對象,通過設置對象的屬性來得到各個不同的個體。 |
(2):操作數棧也是一個數組,但是通過棧操作來訪問。所謂操作數是那些被指令操作的數據。當需要對參數操作時如a=b+c,就將即將被操作的參數壓棧,如將b 和c 壓棧,然后由操作指令將它們彈出,并執行操作。虛擬機將操作數棧作為工作區。
(3):幀數據區處理常量池解析,異常處理等
?
1.3 java堆?
? ? ? java的堆是一個運行時的數據區,用來存儲數據的單元,存放通過new關鍵字新建的
對象
和
數組
,對象從中分配內存。
? ? ? 在堆中聲明的對象,是不能直接訪問的,必須通過在棧中聲明的指向該引用的變量來調用。引用變量就相當于是為數組或對象起的一個名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。
? ?
? ? ?由此我們可以知道,引用類型變量和對象的區別:?
聲明的對象是在堆內存中初始化的, 真正用來存儲數據的。不能直接訪問。
引用類型變量是保存在棧當中的,一個用來引用堆中對象的符號而已(指針)。 |
堆與棧的比較
:
JAVA堆與棧都是用來存放數據的,那么他們之間到底有什么差異呢?既然棧也能存放數據,為什么還要設計堆呢?
1. 從存放數據的角度:
? ? ?? 前面我們已經說明:
? ? ? 棧中存放的是基本類型的變量 or 引用類型的變量
? ? ? ? 堆中存放的是對象 or 數組對象.
? ? ? ?在棧中,引用變量的大小為32位,基本類型為1-8個字節。
? ? ? ?但是對象的大小和數組的大小是動態的,這也決定了堆中數據的動態性,因為它是在運行時動態分配內存的,
生存期也不必在編譯時確定,
Java 的垃圾收集器會自動收走這些不再使用的數據。
?
2. 從數據共享的角度 :
? ? 1).在單個線程類,棧中的數據可共享
? ? 例如我們定義:
- int ?a= 3 ; ??
- int ?b= 3 ;??
int a=3; int b=3;
?
? ? 編譯器先處理int a = 3;首先它會在棧中創建一個變量為a 的引用,然后查找棧中是否有3 這個值,如果沒找到,就將3 存放進來,然后將a 指向3。接著處理int b = 3;在創建完b 的引用變量后,因為在棧中已經有3這個值,便將b 直接指向3。這樣,就出現了a 與b 同時均指向3的情況。
? ? 而如果我們定義: ?
- Integer?a= new ?Integer( 3 ); //(1) ??
- Integer?b= new ?Integer( 3 ); //(2) ??
Integer a=new Integer(3);//(1) Integer b=new Integer(3);//(2)
? ?
這個時候執行過程為:在執行(1)時,首先在棧中創建一個變量a,然后在堆內存中實例化一個對象,并且將變量a指向這個實例化的對象。在執行(2)時,過程類似,此時,在堆內存中,會有兩個Integer類型的對象。?
?
? ? 2). 在進程的各個線程之間,數據的共享通過堆來實現
? ? ? ?
例:那么,在多線程開發中,我們的數據共享又是怎么實現的呢?
?
? 如圖所示,堆中的數據是所有線程棧所共享的,我們可以通過參數傳遞,將一個堆中的數據傳入各個棧的工作內存中,從而實現多個線程間的數據共享
(多個進程間的數據共享則需要通過網絡傳輸了。)?
?
?
3.從程序設計的的角度:
從軟件設計的角度看,JVM棧代表了處理邏輯,而JVM堆代表了數據。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設計的方方面面都有體現。
4.值傳遞和引用傳遞的真相
有了以上關于棧和堆的種種了解后,我們很容易就可以知道值傳遞和引用傳遞的真相:
?
1.程序運行永遠都是在JVM棧中進行的,因而參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。 但是傳引用的錯覺是如何造成的呢? 在運行JVM棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調用,也同時可以理解為“傳引用值”的傳值調用,即引用的處理跟基本類型是完全一樣的。 但是當進入被調用方法時,被傳遞的這個引用的值,被程序解釋(或者查找)到JVM堆中的對象,這個時候才對應到真正的對象。 如果此時進行修改,修改的是引用對應的對象,而不是引用本身,即:修改的是JVM堆中的數據。所以這個修改是可以保持的了。 |
?
?
最后:
從某種意義上來說 對象都 是由基本類型組成的。 ?
?
可以把一個對象看作為一棵樹,對象的屬性如果還是對象,則還是一顆樹(即非葉子節點),基本類型則為樹的葉子節點。程序參數傳遞時,被傳遞的值本身都是不能進行修改的,但是,如果這個值是一個非葉子節點(即一個對象引用),則可以修改這個節點下面的所有內容。? |
?
?
?
其實,面向對象方式的程序與以前結構化的程序在執行上沒有任何區別 。
面向對象的引入,只是 改變了我們 對待問題的思考方式,而更接近于自然方式的思考。
當我們把對象拆開,其實對象的屬性就是數據,存放在JVM堆中;而對象的行為(方法),就是運行邏輯,放在JVM棧中。我們在編寫對象的時候,其實即編寫了數據結構,也編寫的處理數據的邏輯。?
?
?
P.S
?
關于數組的內存分配,對象初始化的內存分配等問題,由于篇幅問題,下次再搞個專題寫吧。 ?連續幾天幾夜對著此文了。想吐的很,先到這里吧。 |
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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