我的Thinking in Java學習筆記(七)
來源:互聯網
上載者:User
清理:終結與記憶體回收
這裡要明白這麼幾點
1、記憶體回收行程只能回收由new產生的對象,如果你使用或產生了非new產生的對象,記憶體回收行程是不知道如何把他清理掉的 。這個時候就要使用到finalize()。
2、記憶體回收行程的運做方式是這樣的,當記憶體回收行程打算開始釋放你的對象所佔用的資源時,會先調用finalize(),並且在下一次記憶體回收動作放生的時候才回收該對象所佔用的資源,如果使用finalize(),他便會讓你得以在記憶體回收的時候執行你自己的清理動作
3、finalize()是不會在對象銷毀的時候自動喚起的。假設有一個對象在產生的過程中將自己繪製在螢幕之上。你要是沒有手動的把他清理掉,那麼他永遠會在那裡,而如果你將清理螢幕的功能放在finalize()之中,那麼當這個對象被記憶體回收行程回收之前,他在螢幕上的影象會先被清除
4、你的對象永遠可能都不會被回收,因為你的程式並沒有把系統資源佔用到需要記憶體回收行程出馬的狀態,你所佔用的系統資源會在程式結束啟動並執行時候全部釋放,那麼你就不用付出記憶體回收行程啟動並執行額外系統開支。
finalize()存在是為了什嗎?
要知道,finalize()不是用於一般的清理工作,記憶體回收行程回清理那些使用new產生的對象,而那些非常規方法(c/c++原生函數)產生對象才會需要
finalize()來進行清理工作。既然這樣,我們就不應該大量的使用finalize(),因為他不是擺放常規清理動作的執行場所。
你必須執行清理動作
那麼那裡才是正常清理動作的執行場所呢?
在c++中,當對象以stack中建立得時候,那麼有一個叫析構的函數會自動的在該對象產生地點所在的大括弧範圍結束前自動調用,如果這個對象是以new的方式從heap中建立的話,那麼就必須調用delete運算子才能清理對象。而java不允許對象從stack中建立,你一定得用new從heap中建立對象才行,但是java並沒有delete運算子,而是靠的是記憶體回收行程來清理對象,所以,我們可以說,既然java中有了記憶體回收行程,也就不需要解構函式。但是記憶體回收行程畢竟不是解構函式,他無法代替解構函式的作用。如果你除了釋放空間之外還要執行其他的清理動作,那麼你就要自行調用類似於解構函式的函數
死亡條件
只要程式不依靠於調用finalize(),那麼這個函數還有一個用途,就是對對象的死亡條件的檢查。
那麼,什麼是死亡條件呢?在對象被清理的時候,該對象一定是要處於某種狀態下才能會被正常的清理,也就是假如有一個資料流已經不再使用了,需要清理掉,那麼你再清理之前,先要被關閉掉,這就是一種死亡條件
class myclass
{
boolean b = false;
myclass(boolean b)
{
this.b=b;
}
void death()
{
b=false;
}
public void finalize()
{
if(b) //死亡條件就是b必須要是false
System.out.println("Error : b is true ,class can't be clean");
else
System.out.println("OK ! CleanUp");
}
}
class test
{
public static void main(String args[])
{
myclass mc = new myclass(true);
mc.death();
new myclass(true);
System.gc(); //System.gc()被用來強迫終結動作的發生,不過即使沒有使用他,只要你能把系統資源佔用到必須記憶體回收行程出馬的時候,finalize()還是會被執行的
}
}
記憶體回收行程的運做方式
學過程式的人應該都知道,從heap中建立對象(基礎資料型別 (Elementary Data Type)除外)的做法,會大幅度的影響系統的速度,然而記憶體回收行程的竟然能大幅度的提高從heap中建立對象的效率,你也許會很驚奇,記憶體回收行程的儲存空間的釋放竟然能影響到儲存空間的分配,還有更驚奇的,這種從heap中建立對象的方式的速度已經逼近其他語言從stack中建立的速度了!
為什麼會這樣子呢?想象一下,一個倒黴的傢伙(對象)站一個公交車(c++的heap)上,而其他的人都有座位,他需要不停的環視車內,希望能有沒人做的座位(高的代價),一單某個人(對象)離開座位(被釋放),他就會衝上去坐好。而在java的jvm中,公交車的座位變成了傳送帶,當有空的座位的時候,後面的人補上前面的座位,依次類推,而那個倒黴的人只要直接的前往最後一個被置空的座位就行了,而不需要他在不停的環視周圍。記憶體回收行程會重新排列heap中的所有對象,使他們更緊密的排列在一起,這樣就能使heap指標移至更靠近傳送帶前段的位置,避免記憶體分頁置換動作,效率大大提高。
gc為了取得更快的記憶體回收速度,於是他會在static和stack中尋找每個對象的控制代碼是否指向一個heap來判斷該對象是否被引用,是否該清理,清理之後會把更改的控制代碼重新對應排列。因為gc的這種特殊性,當gc啟動的時候,執行中的程式會暫時停止。
成員初始化
在java中,class的基礎資料型別 (Elementary Data Type)的資料成員變數會被系統自動初始化,而當變數被定義在函數之中的時候,他是不會被系統自動初始化的
class test
{
byte b;
char c;
short s;
int i;
long l;
float f;
double d;
boolean bl;
void go()
{
int ii;
//System.out.println(ii);當變數被定義在函數之中的時候,他是不會被系統自動初始化的
ii=10;
System.out.println(ii);
System.out.print(
"byte : "+b+"/n"+
"char : "+c+"/n"+
"short : "+s+"/n"+
"int : "+i+"/n"+
"long : "+l+"/n"+
"float : "+f+"/n"+
"double : "+d+"/n"+
"boolean : "+bl+"/n");
}
public static void main(String args[])
{
test t = new test();
t.go();
}
}
結果
10
byte : 0
char : char的值為0,顯示空白
short : 0
int : 0
long : 0
float : 0.0
double : 0.0
boolean : false
指定初值
怎麼樣指定初指,這個我想我不用再寫了吧~這個大家肯定知道
以建構函式進行初始化動作
建構函式可以用來執行初始化動作,因而你有更大的彈性,但是建構函式的初始化動作是發生再自動初始化之後的
class test
{
int i;
test()
{
i=10;
}
}
那麼,i先會被自動初始化為0,然後才會被建構函式初始化為10,就連定義變數的時候就給定初始值的時候也是一樣的,這點需要注意
初始化次序
變數的初始化順序取決於class中的定義變數的次序,變數也許會散落各處,穿插在函數之中,但是所有的變數一定在任何函數,哪怕是建構函式被調用之前完成初始化
待用資料的初始化
static的基礎資料型別 (Elementary Data Type)的資料初始化情況和non-static的基礎資料型別 (Elementary Data Type)沒什麼不同的,但是假如他是某個對象的控制代碼,那麼初始值就是null。static的初始化動作只會在必要的時候發生,如果你沒有產生class對象,也沒有調用靜態資料,則static的資料永遠不會被初始化。static的初始化只會發生在第一個static的訪問動作發生的時候,自此之後,static對象便不會再被初始化
如果static並沒有在對象產生的時候初始化,那麼變數的初始化順序就變為static、non-static、method
static明確初始化
java中允許你將多個static的變數組織起來,放在static 塊中
static
{
int i =10;
byte b= 20;
}
當你首次產生class對象,或者首次調用該類的static成員,static 塊就被初始化
non-static實體初始化動作
java中也為非靜態變數初始化提供了類似於static的方法
{
int i=10;
byte b=10;
}
如果我們想產生無名的內隱類,這個方法是必須的(第8章會講到)