大漠駝鈴

          置身浩瀚的沙漠,方向最為重要,希望此blog能向大漠駝鈴一樣,給我方向和指引。
          Java,Php,Shell,Python,服務(wù)器運(yùn)維,大數(shù)據(jù),SEO, 網(wǎng)站開發(fā)、運(yùn)維,云服務(wù)技術(shù)支持,IM服務(wù)供應(yīng)商, FreeSwitch搭建,技術(shù)支持等. 技術(shù)討論QQ群:428622099
          隨筆 - 238, 文章 - 3, 評論 - 117, 引用 - 0
          數(shù)據(jù)加載中……

          JVM學(xué)習(xí)之-棧

          JVM棧解決程序的運(yùn)行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù);JVM堆解決的是數(shù)據(jù)存儲的問題,即數(shù)據(jù)怎么放、放在哪兒,另外JVM堆中存的是對象。JVM棧中存的是基本數(shù)據(jù)類型和JVM堆中對象的引用。

          JVM基礎(chǔ)概念:JVM堆與JVM

          數(shù)據(jù)類型

          Java虛擬機(jī)中,數(shù)據(jù)類型可以分為兩類:基本類型和引用類型。基本類型的變量保存原始值,即:他代表的值就是數(shù)值本身;而引用類型的變量保存引用值。引用值代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。

          基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress

          引用類型包括:類類型,接口類型和數(shù)組。

          JVM堆與JVM

          JVM堆和JVM棧是程序運(yùn)行的關(guān)鍵,很有必要把他們的關(guān)系說清楚。

          JVM棧是運(yùn)行時的單位,而JVM堆是存儲的單位。

          JVM棧解決程序的運(yùn)行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù);JVM堆解決的是數(shù)據(jù)存儲的問題,即數(shù)據(jù)怎么放、放在哪兒。

          Java中一個線程就會相應(yīng)有一個線程JVM棧與之對應(yīng),這點(diǎn)很容易理解,因?yàn)椴煌木€程執(zhí)行邏輯有所不同,因此需要一個獨(dú)立的線程JVM棧。而JVM堆則是所有線程共享的。JVM棧因?yàn)槭沁\(yùn)行單位,因此里面存儲的信息都是跟當(dāng)前線程(或程序)相關(guān)信息的。包括局部變量、程序運(yùn)行狀態(tài)、方法返回值等等;JVM堆只負(fù)責(zé)存儲對象信息。

          為什么要把JVM堆和JVM棧區(qū)分出來呢?JVM棧中不是也可以存儲數(shù)據(jù)嗎?

          第一,從軟件設(shè)計(jì)的角度看,JVM棧代表了處理邏輯,而JVM堆代表了數(shù)據(jù)。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設(shè)計(jì)的方方面面都有體現(xiàn)。

          第二,JVM堆與JVM棧的分離,使得JVM堆中的內(nèi)容可以被多個JVM棧共享(也可以理解為多個線程訪問同一個對象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存),另一方面,JVM堆中的共享常量和緩存可以被所有JVM棧訪問,節(jié)省了空間。

          第三,JVM棧因?yàn)檫\(yùn)行時的需要,比如保存系統(tǒng)運(yùn)行的上下文,需要進(jìn)行地址段的劃分。由于JVM棧只能向上增長,因此就會限制住JVM棧存儲內(nèi)容的能力。而JVM堆不同,JVM堆中的對象是可以根據(jù)需要動態(tài)增長的,因此JVM棧和JVM堆的拆分,使得動態(tài)增長成為可能,相應(yīng)JVM棧中只需記錄JVM堆中的一個地址即可。

          第四,面向?qū)ο缶褪?/span>JVM堆和JVM棧的完美結(jié)合。其實(shí),面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒有任何區(qū)別。但是,面向?qū)ο蟮囊耄沟脤Υ龁栴}的思考方式發(fā)生了改變,而更接近于自然方式的思考。當(dāng)我們把對象拆開,你會發(fā)現(xiàn),對象的屬性其實(shí)就是數(shù)據(jù),存放在JVM堆中;而對象的行為(方法),就是運(yùn)行邏輯,放在JVM棧中。我們在編寫對象的時候,其實(shí)即編寫了數(shù)據(jù)結(jié)構(gòu),也編寫的處理數(shù)據(jù)的邏輯。不得不承認(rèn),面向?qū)ο蟮脑O(shè)計(jì),確實(shí)很美。

          JVM堆中存什么?JVM棧中存什么?

          JVM堆中存的是對象。JVM棧中存的是基本數(shù)據(jù)類型和JVM堆中對象的引用。一個對象的大小是不可估計(jì)的,或者說是可以動態(tài)變化的,但是在JVM棧中,一個對象只對應(yīng)了一個4btye的引用(JVMJVM棧分離的好處:))

          為什么不把基本類型放JVM堆中呢?因?yàn)槠湔加玫目臻g一般是1~8個字節(jié)——需要空間比較少,而且因?yàn)槭腔绢愋停圆粫霈F(xiàn)動態(tài)增長的情況——長度固定,因此JVM棧中存儲就夠了,如果把他存在JVM堆中是沒有什么意義的(還會浪費(fèi)空間,后面說明)。可以這么說,基本類型和對象的引用都是存放在JVM棧中,而且都是幾個字節(jié)的一個數(shù),因此在程序運(yùn)行時,他們的處理方式是統(tǒng)一的。但是基本類型、對象引用和對象本身就有所區(qū)別了,因?yàn)橐粋€是JVM棧中的數(shù)據(jù)一個是JVM堆中的數(shù)據(jù)。最常見的一個問題就是,Java中參數(shù)傳遞時的問題。

          Java中的參數(shù)傳遞時傳值呢?還是傳引用?

          要說明這個問題,先要明確兩點(diǎn):

          1.不要試圖與C進(jìn)行類比,Java中沒有指針的概念

          2.程序運(yùn)行永遠(yuǎn)都是在JVM棧中進(jìn)行的,因而參數(shù)傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。

          明確以上兩點(diǎn)后。Java在方法調(diào)用傳遞參數(shù)時,因?yàn)闆]有指針,所以它都是進(jìn)行傳值調(diào)用(這點(diǎn)可以參考C的傳值調(diào)用)。因此,很多書里面都說Java是進(jìn)行傳值調(diào)用,這點(diǎn)沒有問題,而且也簡化的C中復(fù)雜性。

          但是傳引用的錯覺是如何造成的呢?在運(yùn)行JVM棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調(diào)用,也同時可以理解為傳引用值的傳值調(diào)用,即引用的處理跟基本類型是完全一樣的。但是當(dāng)進(jìn)入被調(diào)用方法時,被傳遞的這個引用的值,被程序解釋(或者查找)JVM堆中的對象,這個時候才對應(yīng)到真正的對象。如果此時進(jìn)行修改,修改的是引用對應(yīng)的對象,而不是引用本身,即:修改的是JVM堆中的數(shù)據(jù)。所以這個修改是可以保持的了。

          對象,從某種意義上說,是由基本類型組成的。可以把一個對象看作為一棵樹,對象的屬性如果還是對象,則還是一顆樹(即非葉子節(jié)點(diǎn)),基本類型則為樹的葉子節(jié)點(diǎn)。程序參數(shù)傳遞時,被傳遞的值本身都是不能進(jìn)行修改的,但是,如果這個值是一個非葉子節(jié)點(diǎn)(即一個對象引用),則可以修改這個節(jié)點(diǎn)下面的所有內(nèi)容。

          JVM堆和JVM棧中,JVM棧是程序運(yùn)行最根本的東西。程序運(yùn)行可以沒有JVM堆,但是不能沒有JVM棧。而JVM堆是為JVM棧進(jìn)行數(shù)據(jù)存儲服務(wù),說白了JVM堆就是一塊共享的內(nèi)存。不過,正是因?yàn)?/span>JVM堆和JVM棧的分離的思想,才使得Java的垃圾回收成為可能。

          Java中,JVM棧的大小通過-Xss來設(shè)置,當(dāng)JVM棧中存儲數(shù)據(jù)比較多時,需要適當(dāng)調(diào)大這個值,否則會出現(xiàn)java.lang.StackOverflowError異常。常見的出現(xiàn)這個異常的是無法返回的遞歸,因?yàn)榇藭rJVM棧中保存的信息都是方法返回的記錄點(diǎn)。

          java棧的組成元素——棧幀

          棧幀由三部分組成:局部變量區(qū)、操作數(shù)棧、幀數(shù)據(jù)區(qū)。局部變量區(qū)和操作數(shù)棧的大小要視對應(yīng)的方法而定,他們是按字長計(jì)算的。但調(diào)用一個方法時,它從類型信息中得到此方法局部變量區(qū)和操作數(shù)棧大小,并據(jù)此分配棧內(nèi)存,然后壓入Java棧。

          局部變量區(qū):局部變量區(qū)被組織為以一個字長為單位、從0開始計(jì)數(shù)的數(shù)組,類型為shortbytechar的值在存入數(shù)組前要被轉(zhuǎn)換成int值,而longdouble在數(shù)組中占據(jù)連續(xù)的兩項(xiàng),在訪問局部變量中的longdouble時,只需取出連續(xù)兩項(xiàng)的第一項(xiàng)的索引值即可,如某個long值在局部變量區(qū)中占據(jù)的索引時34項(xiàng),取值時,指令只需取索引為3long值即可。

                   說再多也沒用,下面就看個例子,好讓大家對局部變量區(qū)有更深刻的認(rèn)識。這個圖來著《深入JVM》:

          public static int runClassMethod(int i,long l,float f,double d,Object o,byte b) {   
                  
          return 0;   
               }   
                 
              
          public int runInstanceMethod(char c,double d,short s,boolean b) {   
                  
          return 0;   
               }  


          runInstanceMethod的局部變量區(qū)第一項(xiàng)是個reference(引用),它指定的就是對象本身的引用,也就是我們常用的this,但是在runClassMethod方法中,沒這個引用,那是因?yàn)?/span>runClassMethod是個靜態(tài)方法

           

          操作數(shù)棧和局部變量區(qū)一樣,操作數(shù)棧也被組織成一個以字長為單位的數(shù)組。但和前者不同的是,它不是通過索引來訪問的,而是通過入棧和出棧來訪問的。可把操作數(shù)棧理解為存儲計(jì)算時,臨時數(shù)據(jù)的存儲區(qū)域。下面我們通過一段簡短的程序片段外加一幅圖片來了解下操作數(shù)棧的作用。

          Int a= 100;

          Int b = 98;

          Int c = a+b;


          從圖中可以得出:操作數(shù)棧其實(shí)就是個臨時數(shù)據(jù)存儲區(qū)域,它是通過入棧和出棧來進(jìn)行操作的。

          幀數(shù)據(jù)區(qū) 除了局部變量區(qū)和操作數(shù)棧外,java棧幀還需要一些數(shù)據(jù)來支持常量池解析、正常方法返回以及異常派發(fā)機(jī)制。這些數(shù)據(jù)都保存在java棧幀的幀數(shù)據(jù)區(qū)中。當(dāng)JVM執(zhí)行到需要常量池?cái)?shù)據(jù)的指令時,它都會通過幀數(shù)據(jù)區(qū)中指向常量池的指針來訪問它。

          除了處理常量池解析外,幀里的數(shù)據(jù)還要處理java方法的正常結(jié)束和異常終止。如果是通過return正常結(jié)束,則當(dāng)前棧幀從Java棧中彈出,恢復(fù)發(fā)起調(diào)用的方法的棧。如果方法又返回值,JVM會把返回值壓入到發(fā)起調(diào)用方法的操作數(shù)棧。

          為了處理java方法中的異常情況,幀數(shù)據(jù)區(qū)還必須保存一個對此方法異常引用表的引用。當(dāng)異常拋出時,JVMcatch塊中的代碼。如果沒發(fā)現(xiàn),方法立即終止,然后JVM用幀區(qū)數(shù)據(jù)的信息恢復(fù)發(fā)起調(diào)用的方法的幀。然后再發(fā)起調(diào)用方法的上下文重新拋出同樣的異常。

          class Example3C{
              
          public static void addAndPrint(){
                  
          double result = addTwoTypes(1,88.88);
                  System.out.println(result);
              }
              
          public static double addTwoTypes(int i, double d){
              
          return i+d;
              }

          }

          1.只有在調(diào)用一個方法時,才為當(dāng)前棧分配一個幀,然后將該幀壓入棧

          2 幀中存儲了對應(yīng)方法的局部數(shù)據(jù),方法執(zhí)行完,對應(yīng)的幀則從棧中彈出,并把返回結(jié)果存儲在調(diào)用 方法的幀的操作數(shù)棧中


           

           

          posted on 2012-03-15 19:39 草原上的駱駝 閱讀(1341) 評論(0)  編輯  收藏 所屬分類: JAVA基礎(chǔ)知識

          主站蜘蛛池模板: 绩溪县| 北流市| 珲春市| 泰兴市| 文登市| 离岛区| 桃江县| 河间市| 皋兰县| 东乌珠穆沁旗| 靖宇县| 襄樊市| 彭州市| 新兴县| 尼玛县| 德昌县| 株洲县| 合川市| 延安市| 政和县| 扶沟县| 大悟县| 嘉兴市| 绥滨县| 贡山| 仁布县| 沂源县| 汽车| 柘城县| 灵丘县| 高州市| 张掖市| 青阳县| 永福县| 中宁县| 卓资县| 孝感市| 沅陵县| 中山市| 桂林市| 昌邑市|