標籤:返回 對象記憶體 設定 常見 lag heap log 資料 java對象大小
1. 靜態記憶體
靜態記憶體是指在程式開始運行時由編譯器分配的記憶體,它的分配是在程式開始編譯時間完成的,不佔用CPU資源。
程式中的各種變數,在編譯時間系統已經為其分配了所需的記憶體空間,當該變數在範圍內使用完畢時,系統會
自動釋放所佔用的記憶體空間。
變數的分配與釋放,都無須程式員自行考慮。
eg:
基本類型,數組
2. 動態記憶體
使用者無法確定空間大小,或者空間太大,棧上無法分配時,會採用動態記憶體分配。
3. 區別
a) 靜態記憶體配置在編譯時間完成,不佔用CPU資源; 動態記憶體分配在運行時,分配與釋放都佔用CPU資源。
b) 靜態記憶體在棧(stack)上分配; 動態記憶體在堆(heap)上分配。
c) 動態記憶體分配需要指標和參考型別支援,靜態不需要。
d) 靜態記憶體配置是按計劃分配,由編譯器負責; 動態記憶體分配是按需分配,由程式員負責。
4. 執行個體說明
class A{int i;int j;}class TestMemo{public static void main(String[] args){A aa = new A(); // (A *)malloc(sizeof(A));// new A(); 在堆中動態分配一塊地區,被當做了A對象// aa本身的記憶體是在棧中分配的// 堆中記憶體的地址賦給了aa// aa指向堆中的記憶體,aa代表了堆中的記憶體// aa.i 代表: aa這個靜態指標變數所指向的動態記憶體中的A對象的i這個成員// aa.j 代表: aa這個靜態指標變數所指向的動態記憶體中的A對象的j這個成員aa.i = 10; aa.j = 20;System.out.printf("%d, %d\n", aa.i, aa.j);//int i = 10;}}
5. 對象所佔記憶體大小
基本資料的類型的大小是固定的,這裡就不多說了。對於非基本類型的Java對象,其大小就值得商榷。
在Java中,一個空Object對象的大小是8byte,這個大小隻是儲存堆中一個沒有任何屬性的對象的大小。看下面語句:
Object ob = new Object();
這樣在程式中完成了一個Java對象的生命,但是它所佔的空間為:4byte+8byte。4byte是上面部分所說的Java棧中儲存引用的所需要的空間。而那8byte則是Java堆中對象的資訊。
因為所有的Java非基本類型的對象都需要預設繼承Object對象,因此不論什麼樣的Java對象,其大小都必須是大於8byte。
有了Object對象的大小,我們就可以計算其他對象的大小了。
Class NewObject { int count; boolean flag; Object ob;}
其大小為:Null 物件大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因為Java在對對象記憶體配置時都是以8的整數倍來分,因此大於17byte的最接近8的整數倍的是24,因此此對象的大小為24byte。
這裡需要注意一下基本類型的封裝類型的大小。因為這種封裝類型已經成為對象了,因此需要把他們作為對象來看待。封裝類型的大小至少是12byte(聲明一個空Object至少需要的空間),而且12byte沒有包含任何有效資訊,同時,因為Java對象大小是8的整數倍,因此一個基本類型封裝類的大小至少是16byte。這個記憶體佔用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的記憶體佔用更是誇張(隨便想下就知道了)。因此,可能的話應盡量少使用封裝類。在JDK5.0以後,因為加入了自動類型裝換,因此,Java虛擬機器會在儲存方面進行相應的最佳化。
※相關知識點補充
①.資料類型
Java虛擬機器中,資料類型可以分為兩類:基本類型和參考型別。基本類型的變數儲存原始值,即:他代表的值就是數值本身;而參考型別的變數儲存引用值。“引用值”代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。
基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
參考型別包括:類類型,介面類型和數組。
②.堆與棧
棧是運行時的單位,而堆是儲存的單位。
棧解決程式的運行問題,即程式如何執行,或者說如何處理資料;堆解決的是資料存放區的問題,即資料怎麼放、放在哪兒。
在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,因為不同的線程執行邏輯有所不同,因此需要一個獨立的線程棧。而堆則是所有線程共用的。棧因為是運行單位,因此裡面儲存的資訊都是跟當前線程(或程式)相關資訊的。包括局部變數、程式運行狀態、方法傳回值等等;而堆只負責儲存物件資訊。
為什麼要把堆和棧區分出來呢?棧中不是也可以儲存資料嗎?
從軟體設計的角度看,棧代表了處理邏輯,而堆代表了資料。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、模組化的思想在軟體設計的方方面面都有體現。
堆與棧的分離,使得堆中的內容可以被多個棧共用(也可以理解為多個線程訪問同一個對象)。這種共用的收益是很多的。一方面這種共用提供了一種有效資料互動方式(如:共用記憶體),另一方面,堆中的共用常量和緩衝可以被所有棧訪問,節省了空間。
棧因為運行時的需要,比如儲存系統啟動並執行上下文,需要進行位址區段的劃分。由於棧只能向上增長,因此就會限制住棧儲存內容的能力。而堆不同,堆中的對象是可以根據需要動態增長的,因此棧和堆的拆分,使得動態增長成為可能,相應棧中只需記錄堆中的一個地址即可。
物件導向就是堆和棧的完美結合。其實,物件導向方式的程式與以前結構化的程式在執行上沒有任何區別。但是,物件導向的引入,使得對待問題的思考方式發生了改變,而更接近於自然方式的思考。當我們把對象拆開,你會發現,對象的屬性其實就是資料,存放在堆中;而對象的行為(方法),就是運行邏輯,放在棧中。我們在編寫對象的時候,其實即編寫了資料結構,也編寫的處理資料的邏輯。
堆中存什嗎?棧中存什嗎?
堆中存的是對象。
棧中存的是基礎資料型別 (Elementary Data Type)和堆中對象的引用。
一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處:))。
為什麼不把基本類型放堆中呢?因為其佔用的空間一般是1~8個位元組——需要空間比較少,而且因為是基本類型,所以不會出現動態增長的情況——長度固定,因此棧中儲存就夠了,如果把他存在堆中是沒有什麼意義的(還會浪費空間,後面說明)。可以這麼說,基本類型和對象的引用都是存放在棧中,而且都是幾個位元組的一個數,因此在程式運行時,他們的處理方式是統一的。但是基本類型、對象引用和對象本身就有所區別了,因為一個是棧中的資料一個是堆中的資料。最常見的一個問題就是,Java中參數傳遞時的問題。
堆和棧中,棧是程式運行最根本的東西。程式運行可以沒有堆,但是不能沒有棧。而堆是為棧進行資料存放區服務,說白了堆就是一塊共用的記憶體。不過,正是因為堆和棧的分離的思想,才使得Java的記憶體回收成為可能。
Java中,棧的大小通過-Xss來設定,當棧中儲存資料比較多時,需要適當調大這個值,否則會出現java.lang.StackOverflowError異常。常見的出現這個異常的是無法返回的遞迴,因為此時棧中儲存的資訊都是方法返回的記錄點。
Java中的參數傳遞時傳值呢?還是傳引用?
不要試圖與C進行類比,Java中沒有指標的概念
程式運行永遠都是在棧中進行的,因而參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。
但是傳引用的錯覺是如何造成的呢?在運行棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調用,也同時可以理解為“傳引用值”的傳值調用,即引用的處理跟基本類型是完全一樣的。但是當進入被呼叫者法時,被傳遞的這個引用的值,被程式解釋(或者尋找)到堆中的對象,這個時候才對應到真正的對象。如果此時進行修改,修改的是引用對應的對象,而不是引用本身,即:修改的是堆中的資料。所以這個修改是可以保持的了。
對象,從某種意義上說,是由基本類型組成的。可以把一個對象看作為一棵樹,對象的屬性如果還是對象,則還是一顆樹(即非葉子節點),基本類型則為樹的葉子節點。程式參數傳遞時,被傳遞的值本身都是不能進行修改的,但是,如果這個值是一個非葉子節點(即一個對象引用),則可以修改這個節點下面的所有內容。
總結就是只會傳值!傳對象也只是傳入對象的引用的地址這個值!
但是方法內對這個對象進行修改的時候就是修改的對象本身了,而不是改變地址。
③.參考型別
對象參考型別分為強引用、軟引用、弱引用和虛引用。
強引用:就是我們一般聲明對象是時虛擬機器產生的引用,強引用環境下,記憶體回收時需要嚴格判斷當前對象是否被強引用,如果被強引用,則不會被記憶體回收
軟引用:軟引用一般被做為緩衝來使用。與強引用的區別是,軟引用在記憶體回收時,虛擬機器會根據當前系統的剩餘記憶體來決定是否對軟引用進行回收。如果剩餘記憶體比較緊張,則虛擬機器會回收軟引用所引用的空間;如果剩餘記憶體相對富裕,則不會進行回收。換句話說,虛擬機器在發生OutOfMemory時,肯定是沒有軟引用存在的。
弱引用:弱引用與軟引用類似,都是作為緩衝來使用。但與軟引用不同,弱引用在進行記憶體回收時,是一定會被回收掉的,因此其生命週期只存在於一個記憶體回收周期內。
強引用不用說,我們系統一般在使用時都是用的強引用。而“軟引用”和“弱引用”比較少見。他們一般被作為緩衝使用,而且一般是在記憶體大小比較受限的情況下做為緩衝。因為如果記憶體足夠大的話,可以直接使用強引用作為緩衝即可,同時可控性更高。因而,他們常見的是被使用在案頭應用系統的緩衝。
Java靜態記憶體與動態記憶體分配的解析