AJPFX談Java 性能優化之基本類型 vs 引用類型
★名詞定義
先明確一下什麽是“基本類型”,什麽是“引用類型”。
簡單地說,所謂基本類型就是 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 的語言,其實 Java 的 OO 是不夠純粹滴)。
2、由於基本類型不是派生自 Object,出於某些場合(比如容器類)的考慮,不得不為每個基本類型加上對應的包裝類(比如 Integer、Byte 等),使得語言變得有點冗余。
★結論
從上述的介紹,我們應該明白,使用 new 創建對象的開銷是【不小】的。在程序中能避免就應該盡量避免。另外,使用 new 創建對象,不光是創建時開銷大,將來垃圾回收時,銷毀對象也是有開銷的
AJPFX談Java 性能優化之基本類型 vs 引用類型