1. 程式人生 > 實用技巧 >this關鍵字,垃圾回收

this關鍵字,垃圾回收

一 this關鍵字

1   自己的理解是:

1.1 出現在類建立之初的構造器或方法中,而一般不再類具體實現中出現,也不會出現在靜態方法中。

1.2 字面意思是當前物件的引用,你可以像對待其他引用一樣對待這個引用。

2  一些特殊的用法:

2.1 返回this

public class Leaf {

    int i = 0;

    Leaf increment() {
        i++;
        return this;
    }

    void print() {
        System.out.println("i = " + i);
    }

    
public static void main(String[] args) { Leaf x = new Leaf(); x.increment().increment().increment().print(); } }

輸出結果為3,每呼叫一次increment(),返回的物件的元素i加一,最後列印。

2.3 構造器中呼叫構造器

public class Flower {
    int petalCount = 0;
    String s = "initial value";

    Flower(int petals) {
        petalCount 
= petals; System.out.println("Constructor w/ int arg only, petalCount = " + petalCount); } Flower(String ss) { System.out.println("Constructor w/ string arg only, s = " + ss); s = ss; } Flower(String s, int petals) { this(petals); //- this(s); // Can't call two!
this.s = s; // Another use of "this" System.out.println("String & int args"); } Flower() { this("hi", 47); System.out.println("no-arg constructor"); } void printPetalCount() { //- this(11); // Not inside constructor! System.out.println("petalCount = " + petalCount + " s = " + s); } public static void main(String[] args) { Flower x = new Flower(); x.printPetalCount(); } }

在構造器中運用this呼叫構造器可以實現更多奇怪的想法。

注意,只能在構造器中呼叫構造器一次。

結果:

Constructor w/ int arg only, petalCount = 47
String & int args
no-arg constructor
petalCount = 47 s = hi

static簡單說明:靜態方法是為類而建立的,不需要任何物件。事實上,這就是靜態方法的主要目的,靜態方法看起來就像全域性方法一樣,但是 Java 中不允許全域性方法。

使用靜態方法,因為不存在this,所以你沒有向一個物件傳送訊息。不是面向物件的。

如果你發現程式碼中出現了大量的static方法,就該重新考慮自己的設計了。static的概念很實用,許多時候都要用到它。

二 垃圾回收

1 .finalize()方法

  Java垃圾回收器只能回收通過new分配的物件,除此之外需要使用finalize()方法。

  一般在Java使用本地方法時呼叫,本地方法支援呼叫c和c++的程式碼,這些語言可能有垃圾回收器無法回收的物件。

  如果 Java 虛擬機器(JVM)並未面臨記憶體耗盡的情形,它可能不會浪費時間執行垃圾回收以恢復記憶體。

  使用示範:

  

// housekeeping/TerminationCondition.java
// Using finalize() to detect a object that
// hasn't been properly cleaned up

import onjava.*;

class Book {
    boolean checkedOut = false;

    Book(boolean checkOut) {
        checkedOut = checkOut;
    }

    void checkIn() {
        checkedOut = false;
    }

    @Override
    protected void finalize() throws Throwable {
        if (checkedOut) {
            System.out.println("Error: checked out");
        }
        // Normally, you'll also do this:
        // super.finalize(); // Call the base-class version
    }
}

public class TerminationCondition {

    public static void main(String[] args) {
        Book novel = new Book(true);
        // Proper cleanup:
        novel.checkIn();
        // Drop the reference, forget to clean up:
        new Book(true);
        // Force garbage collection & finalization:
        System.gc();
        new Nap(1); // One second delay
    }

}

輸出:Error: checked out

2 垃圾回收器工作方式

2.1 Java堆工作方式

  Java堆擁有和c++語言棧差不多的讀寫速度。

  原因:它類似一個傳送帶,每分配一個新物件,它就向前移動一格。這意味著物件儲存空間的分配速度特別快。Java 的"堆指標"只是簡單地移動到尚未分配的區域,所以它的效率與 C++ 在棧上分配空間的效率相當。

  但是不完全是傳送帶,那樣會導致頻繁的頁面排程,最終記憶體會耗盡。

  解決:垃圾回收器的介入,當它工作時,一邊回收記憶體,一邊使堆中的物件緊湊排列,這樣"堆指標"就可以很容易地移動到更靠近傳送帶的開始處,也就儘量避免了頁面錯誤。

2.2 引用計數

  原理:每個物件中含有一個引用計數器,每當有引用指向該物件時,引用計數加 1。當引用離開作用域或被置為null時,引用計數減 1。垃圾回收器會遍歷含有全部物件的列表,當發現某個物件的引用計數為 0 時,就釋放其佔用的空間。

  缺點:如果物件之間存在迴圈引用,那麼它們的引用計數都不為 0,就會出現應該被回收但無法被回收的情況。(形成閉環)

  該方法幾乎沒有在任何Java虛擬機器中使用。

  

2.3停止-複製

  原理:存活物件追溯到棧或靜態儲存區,每次從棧或靜態儲存區出發,遍歷所有的引用,這個引用鏈條可能會穿過數個物件層次,你將會發現所有"活"的物件。形成一個網,網上的都是活得,不在網上的就會被垃圾回收。

  優點:很好解決物件間迴圈引用的問題,這些物件不會被發現。

  在此方式下形成停止-複製,需要先暫停程式執行,將活的物件複製到另一個堆/空塊,此時還會重新緊密排列,可以按照前面描述的那樣簡單、直接地分配新空間。

  缺點:效率低,所有指向它的引用都必須修正。

  1,得有兩個堆,然後在這兩個分離的堆之間來回折騰,得維護比實際需要多一倍的空間。某些 Java 虛擬機器對此問題的處理方式是,按需從堆中分配幾塊較大的記憶體,複製動作發生在這些大塊記憶體之間。

  2,程式穩定複製減少,垃圾減少或無,此時複製浪費時間佔用資源。解決:要是沒有新垃圾產生,就會轉換到另一種模式“標記-清掃”。

2.4 標記-清掃

  原理,遍歷標記存活物件,但不回收,遍歷完統一處理,未標記直接清理,無複製過程。

  缺點,剩下的堆不連續,要連續空間就需要重新整理剩下的物件。

2.5 注意

  1 垃圾回收不是後臺處理,而是需要程式停止,優先順序很低。

  2 當可用記憶體較低時,垃圾回收器會暫停程式。在具體實施時,標記清掃和停止複製會根據垃圾回收效率自適應切換。

  3"即時"(Just-In-Time, JIT)編譯器。Java虛擬機器提高速度的附加技術,可以把程式全部或部分翻譯成本地機器碼,所以不需要 JVM 來進行翻譯,因此執行得更快。

當需要裝載某個類(通常是建立該類的第一個物件)時,編譯器會先找到其.class檔案,然後將該類的位元組碼裝入記憶體。你可以讓即時編譯器編譯所有程式碼,但這種做法有兩個缺點:一是這種載入動作貫穿整個程式生命週期內,累加起來需要花更多時間;二是會增加可執行程式碼的長度(位元組碼要比即時編譯器展開後的本地機器碼小很多),這會導致頁面排程,從而一定降低程式速度。另一種做法稱為惰性評估,意味著即時編譯器只有在必要的時候才編譯程式碼。這樣,從未被執行的程式碼也許就壓根不會被 JIT 編譯。新版 JDK 中的 Java HotSpot 技術就採用了類似的做法,程式碼每被執行一次就優化一些,所以執行的次數越多,它的速度就越快。

  參考:On Java 8