java之引數傳遞與堆疊-隨筆一
之前看到一個問題:java的引數傳遞到底是值傳遞還是引用傳遞呢?
首先,我們來看看java的堆(heap)和棧(stack)吧!
堆是jvm虛擬機器所管理的記憶體中最大的一塊,主要用來儲存new的物件例項或陣列(當然會有一些別的東西,比如類變數)。java堆是被所有執行緒所共享的一塊記憶體,在java虛擬機器的規範中,java堆可以處於物理上不連續的記憶體空間,只要邏輯上是連續的就好。
棧是執行緒私有的,生命週期與執行緒捆綁在一起。每個java方法在執行的時候都會建立一個棧幀用來儲存區域性變數,運算元棧,動態連結,方法除垢等資訊。每個方法呼叫知道完成執行後的過程,就對應著一個棧幀在虛擬機器棧中的入棧到出棧的過程。區域性變數所需的記憶體空間在編譯期間就能完成分配的,就是說,在進入一個方法時,這個方法在幀中分配的空間是完全確定的,執行不會改變區域性變數的大小。
接下來看看變數的儲存。首先我們看一個圖(不會畫圖,將就著看......)
分析之前加個tips:
在Java中,變數的作用域分為四個級別:類級、物件例項級、方法級、塊級。
類級變數又稱全域性級變數或靜態變數,需要使用static關鍵字修飾。類級變數在類定義後就已經存在,佔用記憶體空間,可以通過類名來訪問,不需要例項化。
物件例項級變數就是成員變數,例項化後才會分配記憶體空間,才能訪問。
方法級變數就是在方法內部定義的變數,就是區域性變數。
說明:
方法內部除了能訪問方法級的變數,還可以訪問類級和例項級的變數。
塊內部能夠訪問類級、例項級變數,如果塊被包含在方法內部,它還可以訪問方法級的變數。
方法級和塊級的變數必須被顯示地初始化,否則不能訪問。
棧中儲存基本型別和引用地址。當你定義一個變數,例如int a=3;則棧中就分配了一個a=3的幀。當你定義的變數的超過作用域時,假設為new一個People物件,這時p升級為例項級變數。這個例項物件超過了會在堆中分配記憶體,這個分配的實體地址可能不連續。同時通過hash演算法生成一串長數字作為引用地址,返回給棧中。於是棧中就有了p=(引用地址),當你找這個物件其實是通過引用地址找堆中的例項物件。也就是當你使用==比較兩個引用型別時,是比較的地址。你沒重寫toString()方法,你輸出的物件或者陣列是個地址。
在堆中的分配的記憶體,將由jvm的垃圾回收器(GC)回收。
再來聊聊java中變數在記憶體中的分配:
類變數(static修飾的變數):在程式載入時系統就為它在堆中開闢了記憶體,堆中的記憶體地址存放於棧以便於高速訪問。靜態變數的生命週期--一直持續到整個"系統"關閉
例項變數:當你使用java關鍵字new的時候,系統在堆中開闢並不一定是連續的空間分配給變數(比如說類例項),然後根據零散的堆記憶體地址,通過雜湊演算法換算為一長串數字以表徵這個變數在堆中的"物理位置"。 例項變數的生命週期--當例項變數的引用丟失後,將被GC(垃圾回收器)列入可回收“名單”中,但並不是馬上就釋放堆中記憶體
區域性變數:區域性變數,由宣告在某方法,或某程式碼段裡(比如for迴圈),執行到它的時候在棧中開闢記憶體,當局部變數一但脫離作用域,記憶體立即釋放。
總結一下:棧的存取速度快,稍遜於暫存器, 比堆快;堆記憶體,負責執行時(runtime, 執行生成的class檔案時)資料,由JVM的自動管理。缺點是,存取速度較慢。 棧儲存基本型別、區域性變數以及引用地址;堆儲存例項物件、類變數、陣列。棧中的引用地址是堆中對應變數的標識。最後,我的認知可能會有偏差,希望讀者能提出改進或者支出錯誤。
回到一開始的問題:java的引數傳遞到底是值傳遞還是引用傳遞呢?當傳入的是基本資料型別時,傳入的是變數的值;當傳入的是引用型別時,傳入的是引用地址。我們應該明確一點:程式執行過程中永遠是在棧內執行,所以java的引數傳遞只能傳基本資料型別和物件的引用,不會傳物件本身。