在Java效能最佳化系列中,記憶體管理是一個要優先考慮的關鍵因素。而說到記憶體配置,就必然會涉及到基本類型和參考型別。所以我們今天就先來介紹一下這兩種類型在效能方面各自有什麼奧妙(關於參考型別的其它奧妙,請看“這裡”)。
★名詞定義
先明確一下什麼是基本類型,什麼是參考型別。簡單地說,所謂基本類型就是Java語言中如下的8種內建類型:boolean、char、byte、short、int、long、float、double。而參考型別就是那些可以通過new來建立對象的類型(基本上都是派生自Object)。
★兩種類型的儲存方式
這兩種類型的差異,首先體現在儲存方式上。
◇參考型別的建立
當你在函數中建立一個參考型別的對象時,比如下面的語句:
StringBuffer str = new StringBuffer();
該StringBuffer對象的內容是儲存在堆(Heap)上的,需要申請堆記憶體。而變數str只不過是針對該StringBuffer對象的一個引用(或者叫地址)。變數str的值(也就是StringBuffer對象的地址)是儲存在棧上的。
◇基本類型的建立
當你在函數中建立一個基本類型的變數時,比如如下語句:
int n = 123;
這個變數n的值也是儲存在棧(Stack)上的,但是這個語句不需要再從堆中申請記憶體了。
為了更加形象,便於大伙兒理解,簡單畫了一個如下:
★堆和棧的效能差異
可能有同學會小聲問:堆和棧有啥區別捏?要說堆和棧的差別,那可就大了去了。如果你對這兩個概念還是不太明白或者經常混淆,建議先找本作業系統的書拜讀一下。
由於本系列是介紹效能,所以來討論一下堆和棧在效能方面的差別(這個差異是很大滴)。堆相對進程來說是全域的,能夠被所有線程訪問;而棧是線程局部的,只能本線程訪問。打個比方,棧就好比個人小金庫,堆就好比國庫。你從個人小金庫拿錢去花,不需要辦什麼手續,拿了就花,但是錢數有限;而國庫裡面的錢雖然很多,但是每次申請花錢要打報告、蓋圖章、辦N多手續,耗時又費力。
同樣道理,由於堆是所有線程共有的,從堆裡面申請記憶體要進行相關的加鎖操作,因此申請堆記憶體的複雜度和時間開銷比棧要大很多;從棧裡面申請記憶體,雖然又簡單又快,但是棧的大小有限,分配不了太多記憶體。
★為什麼這樣設計?
可能有同學又問了,幹嘛把兩種類型分開儲存,幹嘛不放到一起捏?這個問題問得好!下面我們就來揣測一下,當初Java為啥設計成這樣。
當年Java它爹(James Gosling)設計語言的時候,對於這個問題有點進退兩難。如果把各種東東都放置到棧中,顯然不現實,一來棧是線程私人的(不便於共用),二來棧的大小是有限的,三來棧的結構也間接限制了它的用途。那為啥不把各種東東都放置到堆裡面捏?都放堆裡面,倒是能繞過上述問題,但是剛才也提到了,申請堆記憶體要辦很多手續,太繁瑣。如果僅僅在函數中寫一個簡單的“int
n = 0;”,也要到堆裡面去分配記憶體,那效能就大大滴差了(要知道Java是1995年生出來的,那年頭我家的PC配4兆記憶體就屬豪華配置了)。
左思右想之後,Java它爹只好做了一個折中:把類型分為基本類型和參考型別,兩者使用不同的建立方式。這種差異從Java文法上也可以看出來:參考型別可以用new建立對象(對於某些單鍵,表面上沒用new,但是在getInstance()內部也還是用的new);而基本類型則不需要用new來建立。
★這樣設計的弊端
順便跑題一下,鬥膽評價Java它爹這種設計的弊端(希望Java Fans不要跟我急)。我個人認為:這個折中的決策,帶來了許多深遠的影響,隨手舉出幾個例子:
1、由於基本類型不是派生自Object,因此不能算是純種的對象。這導致了Java的“純物件導向”招牌打了折扣(當年Sun老是吹噓Java是純OO的語言)。
2、由於基本類型不是派生自Object,出於某些場合(比如容器類)的考慮,不得不為每個基本類型加上對應的封裝類(比如Integer、Byte等),使得語言變得有點冗餘。
★結論
從上述的介紹,我們應該明白,使用new建立對象的開銷是不小的。在程式中能避免就應該盡量避免。另外,使用new建立對象,不光是建立時開銷大,將來記憶體回收時,銷毀對象也是有開銷的(關於GC的開銷,咱們會在後面的文章細談)。下一個文章,我們找一個例子來實戰一下。
著作權聲明
本部落格所有的原創文章,作者皆保留著作權。轉載必須包含本聲明,保持本文完整,並以超連結形式註明作者編程隨想和本文原始地址:
http://program-think.blogspot.com/2009/03/java-performance-tuning-1-two-types.html