2014-10-07 58 views
1

考慮下面的Java類:Java可以檢測何時可以顯式釋放對象?

class Main 
{ 
    private static Integer AddOne(Integer i) { 
     return i + 1; 
    } 
    public static void main(String[] args) 
    { 
     Integer one = new Integer(1); 
     System.out.println(AddOne(one)); 
    } 
} 

在這個簡單的例子,很容易看到該對象提到了one從不調用AddOne後再次引用。在main退出後的一段時間將收集垃圾。然而,將內存放在內存中直到它被收購是一種浪費 - 對於Integer來說可能不是那麼多,但是如果我有大量的數據結構(或其中的許多),則可能會影響性能。

在我看來,編譯器可以檢查上面的代碼,並看到不可能引用one來跳過Main類。然後,它可以明確地在main函數的末尾釋放/完成參考。

這樣的設施是否存在於任何Java實現中?如果沒有,那麼這種機制不起作用的原因是否有技術上的原因?

回答

1

JIT和Java垃圾收集的最新進展使這類問題的確切答案變得困難。但由於language specification的要求,JVM必須支持一些事情。

尤其是,即使字節碼未觸及實例變量(例如未使用的專用字段),JVM也無法收集實例變量,因爲調用者仍然可以使用反射來檢查或訪問元素。

在你的例子中,我們討論的是一個局部變量,它的處理方式不同。局部變量被分配在堆棧中,而不是堆中,只要方法返回,垃圾收集器就沒有問題清理它(如果事實上甚至更早)。它不需要等到Main類被卸載。 Integer對象one表示將保持比one本身更長的時間,但只要它未被引用,GC就會在第一時間清理它。

我不知道現在是否這是真的,但我會想象編譯器甚至可能重新設計這樣的代碼,以完全避免Integer,並直接使用int。本示例中的new調用可能會明確阻止此處的操作,但用Integer.valueOf()代替將避免顯式的分配請求,並可能允許編譯器根本避免任何對象分配。

這當然,在這個簡單的例子中,GC或JIT不太可能有足夠的時間來嘗試清理任何東西。顯然你只是爲了討論而提供一個簡單的例子,但值得一提的是,該計劃的持續時間可能會對GC'ed或JIT''什麼時候產生重大影響,以及何時發生。

如果我有時間我會嘗試用一些示例代碼/分析重新回顧這個答案,那麼使用WeakReference來模擬/測試其中的一些可能是可能的。

您還可以欣賞類似的問題,我問去年:Can unused private variables be GCed before their holding instance?

+1

注意參考變量走出去的範圍是不一樣的東西被引用的對象是供GC。在變量的生命期間,可能已經創建了對該對象的其他引用。 – 2014-10-07 04:04:04

+0

@TedHopp感謝您指出的區別;不同的規則絕對適用於「一」和「對象」以前稱爲「一」。 – dimo414 2014-10-07 04:30:09

1

這是不可能的(至少對於Java虛擬機),因爲沒有顯式釋放對象的JVM指令。 (該指令集可在Chapter 6 of the Java Virtual Machine Specification中找到。)讓GC完成工作真的沒有太多浪費。大多數實現都使用增量GC算法,它們非常適合注意何時相當快速地超出範圍。

我非常確定編譯器和JIT編譯器在識別死變量方面非常出色,因此one在上次使用後經常會超出範圍(以及受到GC控制的Integer),無論其他操作如何在main()

雖然在您的示例中,編譯器可能會發現one引用的對象符合GC的條件,但這依賴於分析AddOne的操作以確保不會創建對該對象的其他引用。即使代碼的複雜性稍微增加,也會使這種靜態分析幾乎不可能。因此,即使編譯器可以按照您的建議進行操作,我認爲分析的複雜性會使其毫無用處地嘗試這樣做。

一個進一步的思考:如果你改變了one的初始化:

Integer one = Integer.valueOf(1); 

那麼這將是絕對錯誤的Integer到的main()末被釋放。這是因爲Integer.valueOf(1)幾乎肯定會返回由Integer類維護的緩存對象。由於高速緩存的原因,除非卸載Integer類定義(通常在JVM關閉期間),否則引用計數不會爲0。

相關問題