程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 必須執行清除

必須執行清除

編輯:關於JAVA

為清除一個對象,那個對象的用戶必須在希望進行清除的地點調用一個清除方法。這聽起來似乎很容易做到,但卻與C++“破壞器”的概念稍有抵觸。在C++中,所有對象都會破壞(清除)。或者換句話說,所有對象都“應該”破壞。若將C++對象創建成一個本地對象,比如在堆棧中創建(在Java中是不可能的),那麼清除或破壞工作就會在“結束花括號”所代表的、創建這個對象的作用域的末尾進行。若對象是用new創建的(類似於Java),那麼當程序員調用C++的delete命令時(Java沒有這個命令),就會調用相應的破壞器。若程序員忘記了,那麼永遠不會調用破壞器,我們最終得到的將是一個內存“漏洞”,另外還包括對象的其他部分永遠不會得到清除。
相反,Java不允許我們創建本地(局部)對象——無論如何都要使用new。但在Java中,沒有“delete”命令來釋放對象,因為垃圾收集器會幫助我們自動釋放存儲空間。所以如果站在比較簡化的立場,我們可以說正是由於存在垃圾收集機制,所以Java沒有破壞器。然而,隨著以後學習的深入,就會知道垃圾收集器的存在並不能完全消除對破壞器的需要,或者說不能消除對破壞器代表的那種機制的需要(而且絕對不能直接調用finalize(),所以應盡量避免用它)。若希望執行除釋放存儲空間之外的其他某種形式的清除工作,仍然必須調用Java中的一個方法。它等價於C++的破壞器,只是沒後者方便。
finalize()最有用處的地方之一是觀察垃圾收集的過程。下面這個例子向大家展示了垃圾收集所經歷的過程,並對前面的陳述進行了總結。
 

//: Garbage.java
// Demonstration of the garbage
// collector and finalization

class Chair {
  static boolean gcrun = false;
  static boolean f = false;
  static int created = 0;
  static int finalized = 0;
  int i;
  Chair() {
    i = ++created;
    if(created == 47) 
      System.out.println("Created 47");
  }
  protected void finalize() {
    if(!gcrun) {
      gcrun = true;
      System.out.println(
        "Beginning to finalize after " +
        created + " Chairs have been created");
    }
    if(i == 47) {
      System.out.println(
        "Finalizing Chair #47, " +
        "Setting flag to stop Chair creation");
      f = true;
    }
    finalized++;
    if(finalized >= created)
      System.out.println(
        "All " + finalized + " finalized");
  }
}

public class Garbage {
  public static void main(String[] args) {
    if(args.length == 0) {
      System.err.println("Usage: \n" +
        "java Garbage before\n  or:\n" +
        "java Garbage after");
      return;
    }
    while(!Chair.f) {
      new Chair();
      new String("To take up space");
    }
    System.out.println(
      "After all Chairs have been created:\n" +
      "total created = " + Chair.created +
      ", total finalized = " + Chair.finalized);
    if(args[0].equals("before")) {
      System.out.println("gc():");
      System.gc();
      System.out.println("runFinalization():");
      System.runFinalization();
    }
    System.out.println("bye!");
    if(args[0].equals("after"))
      System.runFinalizersOnExit(true);
  }
} ///:~


上面這個程序創建了許多Chair對象,而且在垃圾收集器開始運行後的某些時候,程序會停止創建Chair。由於垃圾收集器可能在任何時間運行,所以我們不能准確知道它在何時啟動。因此,程序用一個名為gcrun的標記來指出垃圾收集器是否已經開始運行。利用第二個標記f,Chair可告訴main()它應停止對象的生成。這兩個標記都是在finalize()內部設置的,它調用於垃圾收集期間。
另兩個static變量——created以及finalized——分別用於跟蹤已創建的對象數量以及垃圾收集器已進行完收尾工作的對象數量。最後,每個Chair都有它自己的(非static)int i,所以能跟蹤了解它具體的編號是多少。編號為47的Chair進行完收尾工作後,標記會設為true,最終結束Chair對象的創建過程。
所有這些都在main()的內部進行——在下面這個循環裡:

while(!Chair.f) {
new Chair();
new String("To take up space");
}

大家可能會疑惑這個循環什麼時候會停下來,因為內部沒有任何改變Chair.f值的語句。然而,finalize()進程會改變這個值,直至最終對編號47的對象進行收尾處理。
每次循環過程中創建的String對象只是屬於額外的垃圾,用於吸引垃圾收集器——一旦垃圾收集器對可用內存的容量感到“緊張不安”,就會開始關注它。
運行這個程序的時候,提供了一個命令行自變量“before”或者“after”。其中,“before”自變量會調用System.gc()方法(強制執行垃圾收集器),同時還會調用System.runFinalization()方法,以便進行收尾工作。這些方法都可在Java 1.0中使用,但通過使用“after”自變量而調用的runFinalizersOnExit()方法卻只有Java 1.1及後續版本提供了對它的支持(注釋③)。注意可在程序執行的任何時候調用這個方法,而且收尾程序的執行與垃圾收集器是否運行是無關的。

③:不幸的是,Java 1.0采用的垃圾收集器方案永遠不能正確地調用finalize()。因此,finalize()方法(特別是那些用於關閉文件的)事實上經常都不會得到調用。現在有些文章聲稱所有收尾模塊都會在程序退出的時候得到調用——即使到程序中止的時候,垃圾收集器仍未針對那些對象采取行動。這並不是真實的情況,所以我們根本不能指望finalize()能為所有對象而調用。特別地,finalize()在Java 1.0裡幾乎毫無用處。

前面的程序向我們揭示出:在Java 1.1中,收尾模塊肯定會運行這一許諾已成為現實——但前提是我們明確地強制它采取這一操作。若使用一個不是“before”或“after”的自變量(如“none”),那麼兩個收尾工作都不會進行,而且我們會得到象下面這樣的輸出:
Created 47

 

Created 47
Beginning to finalize after 8694 Chairs have been created
Finalizing Chair #47, Setting flag to stop Chair creation
After all Chairs have been created:
total created = 9834, total finalized = 108
bye!

因此,到程序結束的時候,並非所有收尾模塊都會得到調用(注釋④)。為強制進行收尾工作,可先調用System.gc(),再調用System.runFinalization()。這樣可清除到目前為止沒有使用的所有對象。這樣做一個稍顯奇怪的地方是在調用runFinalization()之前調用gc(),這看起來似乎與Sun公司的文檔說明有些抵觸,它宣稱首先運行收尾模塊,再釋放存儲空間。然而,若在這裡首先調用runFinalization(),再調用gc(),收尾模塊根本不會執行。

④:到你讀到本書時,有些Java虛擬機(JVM)可能已開始表現出不同的行為。

針對所有對象,Java 1.1有時之所以會默認為跳過收尾工作,是由於它認為這樣做的開銷太大。不管用哪種方法強制進行垃圾收集,都可能注意到比沒有額外收尾工作時較長的時間延遲。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved