看Java中對象引用如何嚴重影響垃圾收集器(2)

一個有問題的例子

即使如此,顯式地賦空變量能夠提高性能嗎?我們會發現我們很難相信一個對象會或多或少對程序的性能產生很大影響,直到我看到了一個在 Java Games 的 Sun 工程師給出的一個例子,這個例子包含了一個不幸的大型對象。

清單3:仍在靜態作用域中的對象

private static Object bigObject;

public static void test(int size) { long startTime = System.currentTimeMillis(); long numObjects = 0; while (true) { //bigObject = null; //explicit nulling //SizableObject could simply be a large array, e.g. byte[] //In the JavaGaming discussion it was a BufferedImage bigObject = new SizableObject(size); long endTime = System.currentTimeMillis(); ++numObjects; // We print stats for every two seconds if (endTime - startTime >= 2000) { System.out.println("Objects created per 2 seconds = " + numObjects); startTime = endTime; numObjects = 0; } } }

這個例子有個簡單的循環,創建一個大型對象並且將它賦給同一個變量,每隔兩秒鐘報告一次所創建的對象個數。現在的 Java 虛擬機採用 generational 垃圾收集機制,新的對象創建之後放在一個內存空間(取名 Eden)內,然後將那些在第一次垃圾收集以後仍然保留的對象轉移到另外一個內存空間。在 Eden,即創建新對象時所在的新一代空間中,收集對象要比在“老一代”空間中快得多。但是如果 Eden 空間已經滿了,沒有空間可供分配,那麼就必須把 Eden 中的對象轉移到老一代空間中,騰出空間來給新創建的對象。如果沒有顯式地賦空變量,而且所創建的對象足夠大,那麼 Eden 就會填滿,並且垃圾收集器就不能收集當前所引用的這個大型對象。所產生的後果是,這個大型對象被轉移到“老一代空間”,並且要花更多的時間來收集它。

通過顯式地賦空變量,Eden 就能在新對象創建之前獲得自由空間,這樣垃圾收集就會更快。實際上,在顯式賦空的情況下,該循環在兩秒鐘內創建的對象個數是沒有顯式賦空時的5倍――但是僅當您選擇創建的對象要足夠大而可以填滿 Eden 時纔是如此, 在 Windows 環境、Java虛擬機 1.4 的默認配置下大概需要 500KB。那就是一行賦空操作產生的 5 倍的性能差距。但是請注意這個性能差別產生的原因是變量的作用域不正確,這正是賦空操作發揮作用的地方,並且是因爲所創建的對象非常大。

最佳實踐

這是一個有趣的例子,但是值得強調的是,最佳實踐是正確地設置變量的作用域,而不要顯式地賦空它們。雖然顯式賦空變量一般應該沒有影響,但總有一些反面的例子證明這樣做會對性能產生巨大的負面影響。例如,迭代地或者遞歸地賦空集合內的元素使得這些集合中的對象能夠滿足垃圾收集的條件,實際上是增加了系統的開銷而不是幫助垃圾收集。請記住這是個有意弄錯作用域的例子,其實質是一個無意識的對象保留的例子。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章