1. java中堆與棧
在Java程序運行時,有6個地方可以用于保存數據:
(1) 寄存器。最快的保存區域,位于處理器內部,數量十分有限,它是根據需要由編譯器分配。我們對此沒有直接的控制權.
(2) 棧(stack)。駐留于常規RAM(隨機訪問存儲器)區域,這是一種特別快、特別有效的數據保存方式,僅次于寄存器。創建程序時,Java編譯器必須準確地知道堆棧內保存的所有數據的“長度”以及“存在時間”。這失去了一定的靈活性,因此對象句柄是存放在棧中,但Java對象并不放到其中。
(3) 堆(heap)。保存了Java對象。和棧不同,它最吸引人的地方在于編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數據要在堆里停留多長的時間。因此,用堆保存數據時會得到更大的靈活性。要求創建一個對象時,只需用new命令編制相關的代碼即可。執行這些代碼時,會在堆里自動進行數據的保存。當然,為達到這種靈活性,必然會付出一定的代價:在堆里分配存儲空間時會花掉更長的時間!
(4) 靜態存儲。這兒的“靜態”(Static)是指“位于固定位置”(盡管也在RAM里)。程序運行期間,靜態存儲的數據將隨時等候調用。可用static關鍵字指出一個對象的特定元素是靜態的。但Java對象本身永遠都不會置入靜態存儲空間。
(5) 常數存儲。常數值通常直接置于程序代碼內部。這樣做是安全的,因為它們永遠都不會改變。有的常數需要嚴格地保護,所以可考慮將它們置入只讀存儲器(ROM)。
(6) 非RAM存儲。數據完全獨立于一個程序之外,則程序不運行時仍可存在,并在程序的控制范圍之外。
2.堆和棧的區別
棧與堆都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設置棧或堆。
Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由于要在運行時動態分配內存,存取速度較慢。
棧的優勢是,存取速度比堆要快,僅次于寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:
int a = 3;
int b = 3;
編譯器先處理int a = 3;首先它會在棧中創建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒找到,就將3存放進來,然后將a指向3。接著處理int b = 3;在創建完b的引用變量后,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。
這時,如果再令a=4;那么編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,并令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改并不會影響到b, 它是由編譯器完成的,它有利于節省空間。而一個對象引用變量修改了這個對象的內部狀態,會影響到另一個對象引用變量。
String是一個特殊的包裝類數據。可以用:
String str = new String("abc");
String str = "abc";
兩種的形式來創建,第一種是用new()來新建對象的,它會在存放于堆中。每調用一次就會創建一個新的對象。
而第二種是先在棧中創建一個對String類的對象引用變量str,然后查找棧中有沒有存放"abc",如果沒有,則將"abc"存放進棧,并令str指向”abc”,如果已經有”abc” 則直接令str指向“abc”。
比較類里面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,下面用例子說明上面的理論。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一個對象的。
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的對象。每一次生成一個。
因此用第二種方式創建多個”abc”字符串,在內存中其實只存在一個對象而已. 這種寫法有利與節省內存空間. 同時它可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對于String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字符串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。
另一方面, 要注意: 我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,創建了String類的對象str。擔心陷阱!對象可能并沒有被創建!而可能只是指向一個先前已經創建的對象。只有通過new()方法才能保證每次都創建一個新的對象。
由于String類的immutable性質,當String變量需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。
參考文獻:
關于Java棧與堆的思考
http://www.duduwolf.com/post/3.asp
3.關于java中數據占用內存空間的大小
――測試環境
j2me midp1.0
――測試方法:
使用下面的語句,獲取對象創建前后的內存值,它們的差值就為所創建對象占用的內存大小:
1.i = Runtime.getRuntime().freeMemory();
2.control = new Control(Control.HEAD_MOVE, 1, 1);
3.j = Runtime.getRuntime().freeMemory();
4.System.out.println(i-j);
這個方法并不準確,因為JVM會不定時在后臺運行一些東西,比如在gc,后臺運行東西會占用一定的內存,如果這個后臺程序的運行發生在1、3句之間的時候就會造成不準確。
為了盡可能保持正確性,將語句變為如下,先gc,再取內存,讓那些由于后臺運行而占用的內存釋放出來。
Runtime.getRuntime().gc();
Thread.yield();
i = Runtime.getRuntime().freeMemory();
test t = new test();
Runtime.getRuntime().gc();
Thread.yield();
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j);
但是這種方法還是不一定能夠保證gc會被調用,因此每一個測試得到的數據都是多次測試得出相同的那個數據,取當堆大小穩定以后顯示的數據,追求盡量準確。
(1) i = Runtime.getRuntime().freeMemory();
int t = 0;
char a ='a';
long l =23410389;
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j); // 0
【結論】因為基本數據類型存放在棧中,并不占用堆的內存,可見freeMemory返回的數值是堆中的內存,因此值為0。
(2)
i = Runtime.getRuntime().freeMemory();
String s2="1234567";
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j); // 0
―――――――――――――――――――――――――――――――
i = Runtime.getRuntime().freeMemory();
String s2=new String("1234567");
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j); // 56
【結論】以String s2="1234567"方式創建的字符串是存放在棧中,它不占用堆空間;而用String s2=new String("1234567");方式創建的字符串是存放在堆中,它占用了56byte的堆空間。
(3)
Runtime.getRuntime().gc();
Thread.yield();
i = Runtime.getRuntime().freeMemory();
test t = new test(); //test為一個空的Class
Runtime.getRuntime().gc();
Thread.yield();
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j); // 12
――――――――――――――――――――――――――――――――
Runtime.getRuntime().gc();
Thread.yield();
i = Runtime.getRuntime().freeMemory();
Contorl c = new Control(Control.HEAD_MOVE, 1, 1);
Runtime.getRuntime().gc();
Thread.yield();
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j); //112
【結論1】:一個空的對象需要占用12byte的堆空間,將其理解為對象頭。
【結論2】:一個zhanguo/Control的一個“HEAD_MOVE”對象占用112byte堆空間。
為何是112byte?
Control中有25個非static的基本數據類型和引用變量,而int,short,byte等基本數據類型和引用變量占用的內存大小都是4byte(詳見4),因此大小為:
25*4 + 12 (對象頭)==112
(4)在上面的Class test中加入: private int a = 0;
得出結果:16byte ,
由(3)可以知空object占12byte,所以一個int占用4byte的空間。
將int改為其他基本數據類型,用同樣方法進行測試,得出如下結論:
【結論1】:float, boolean,byte, short, int, char占用4個byte的空間,
long,double占用8byte空間 (midp1.0不支持float和double)
引用變量,比如 Control c = null, 占用4byte空間。
【結論2】:在java中類型決定行為,而不是大小;用byte起到限制數據的作用,但是并不能節約內存,在內存中byte和int一樣是占用4byte的空間。
【結論3】:雖然a為基本的數據類型,但是它是對象t的一個屬性,是在運行時才動態創建的,因此它也是存放在堆中,占用4byte的堆空間。
(5)在Class test中加入下面語句:并去掉(4)中加入的變量 int a。
private final static int j = 1;
private final static int k = 4;
private static void test(){
}
得出結果:12
【結論1】一個object的占用堆空間的多少只與類中的非static的基本數據類型和引用變量有關,而與static的變量和的多少并沒有關系。Static是存放在“靜態存儲空間”,不占用堆空間。
【結論2】對于需要生成多個object的類,比如control等,類中的成員變量盡量用static,因為object中的非staitc變量在堆中需要占用一定的空間,當object數量比較多時,占用的堆空間會增大很多。從而容易出現內存不足(主要是堆空間不足)造成“應用程序錯誤”的情況。
(6)
i = Runtime.getRuntime().freeMemory();
Strin s=new String("");
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j); //40
【結論】一個空的String就要占用40字節的堆空間,理解為String頭信息,在代碼中要避免反復得new一個相同的string,而是應該利用上面(2)中所講的那種方式:
String s2="1234567"來生成String。
(7)
Runtime.getRuntime().gc();
Thread.yield();
i = Runtime.getRuntime().freeMemory();
int a[][] = new int[0][0];
Runtime.getRuntime().gc();
Thread.yield();
j = Runtime.getRuntime().freeMemory();
System.out.println(i-j); //16
----------------------------------------------------------------------------------
int a[][] = new int[1][0]; //36
int a[][] = new int[2][0]; //56
int a[][] = new int[3][0]; //76
int a[][] = new int[200][0]; //4016
int a[][] = new int[200][1]; //4816 ,因為比上面多了200個int,所以多了800
【結論1】每一個嵌套的int[dim2]都是一個對象,一個int占用4byte空間,每一個對象都有一個16字節的數組對象頭。
【結論2】二維數組的數組頭的內存消耗很大,對于對內存較小的手機,比如nokia舊40系列,最好將二維數組改為用一維數組。
參考文章:
http://www.mywelt.net/?q=node/577
http://istudy.com.ru/Article/Software/Java/20051025/54,81897,0.html
posted on 2007-05-24 10:51
cheng 閱讀(350)
評論(0) 編輯 收藏 所屬分類:
JBS