[英]Static references are cleared--does Android unload classes at runtime if unused?
我有一個特定於Android中類加載/垃圾收集如何工作的問題。 我們現在偶然發現了幾次這個問題,據我所知,Android在這里與普通的JVM不同。
問題在於:我們目前正在嘗試減少應用程序中的單例類,以支持單個根工廠單例,其唯一目的是管理其他管理器類。 如果你願意的話,是一名頂級經理。 這使我們可以輕松地替換測試中的實現,而無需選擇完整的DI解決方案,因為所有活動和服務共享對該根工廠的相同引用。
這是它的樣子:
public class RootFactory {
private static volatile RootFactory instance;
@SuppressWarnings("unused")
private Context context; // I'd like to keep this for now
private volatile LanguageSupport languageSupport;
private volatile Preferences preferences;
private volatile LoginManager loginManager;
private volatile TaskManager taskManager;
private volatile PositionProvider positionManager;
private volatile SimpleDataStorage simpleDataStorage;
public static RootFactory initialize(Context context) {
instance = new RootFactory(context);
return instance;
}
private RootFactory(Context context) {
this.context = context;
}
public static RootFactory getInstance() {
return instance;
}
public LanguageSupport getLanguageSupport() {
return languageSupport;
}
public void setLanguageSupport(LanguageSupport languageSupport) {
this.languageSupport = languageSupport;
}
// ...
}
在Application.onCreate
,即在任何活動或服務啟動之前 , initialize
被調用一次。 現在,問題出在這里: getInstance
方法有時會返回null
- 即使在同一個線程上調用! 聽起來這不是一個能見度問題; 相反,類級別上的靜態單例引用保持似乎實際上已被垃圾收集器清除。 也許我在這里得出結論,但這可能是因為Android垃圾收集器或類加載機制實際上可以在內存稀缺時卸載類,在這種情況下,對單例實例的唯一引用將消失? 我並不是真的深入了解Java的內存模型,但我認為這不應該發生,否則這種實現單例的常用方法對任何JVM都不起作用嗎?
知道為什么會發生這種情況嗎?
PS:可以通過在單個應用程序實例上保留“全局”引用來解決此問題。 事實證明,當應用程序必須在應用程序的整個生命周期內保持對象時,這是可靠的。
UPDATE
顯然我在這里使用volatile會引起一些混亂。 我的目的是確保靜態引用的當前狀態始終對訪問它的所有線程可見。 我必須這樣做,因為我正在編寫並從多個線程中讀取該引用:在一個普通的應用程序中,只在主應用程序線程中運行,但在一個檢測測試運行中,對象被mocks替換,我從檢測線程並在UI線程上讀取它。 我也可以將調用同步到getInstance
,但這更昂貴,因為它需要聲明一個對象鎖。 請參閱在Java中實現單例模式的有效方法是什么? 有關此問題的更詳細討論。
你(@Matthias)和Mark Murphy(@CommonsWare)都說得對,但要點似乎丟失了。 ( volatile
的使用是正確的,類沒有卸載。)
問題的關鍵在於從哪里調用initialize
。
以下是我認為正在發生的事情:
Activity
*初始化 Process
Application
和頂級Activity
getInstance
將返回null
,因為沒有調用initialize
如我錯了請糾正我。
更新 :
我的假設 - 從一個Activity
*調用initialize
- 在這種情況下似乎是錯誤的。 但是,我會留下這個答案,因為這種情況是一個常見的錯誤來源。
我一生中從未見過一個聲明為volatile
的靜態數據成員。 我甚至不確定這意味着什么。
靜態數據成員將一直存在,直到進程終止或直到你擺脫它們為止(例如,將靜態引用置null
)。 一旦所有活動和服務被用戶主動關閉(例如,BACK按鈕)和您的代碼(例如, stopService()
),就可以終止該過程。 如果Android在RAM上非常短暫,即使使用實時組件也可以終止該過程,但這是相當不尋常的。 如果Android認為您的服務已經在后台太長時間,則可以使用實時服務終止該過程,盡管它可能會重新啟動該服務,具體取決於您從onStartCommand()
返回的值。
類沒有被卸載,期間,沒有終止進程。
為了解決@ sergui的其他問題,可能會破壞活動 ,存儲實例狀態(盡管在RAM中,而不是“固定存儲”),以釋放RAM。 Android會在終止活動進程之前執行此操作,但如果它破壞了進程的最后一個活動並且沒有正在運行的服務,則該進程將成為終止的主要候選者。
你的實現唯一有點奇怪的是你使用volatile
。
我已經看到類似的奇怪行為與我自己的代碼涉及消失的靜態變量(我不認為這個問題與volatile關鍵字有任何關系)。 特別是當我初始化一個日志框架(例如Crashlytics,log4j),然后經過一段時間的活動后,它似乎未初始化。 調查顯示,在OS調用onSaveInstanceState(Bundle b)
之后會發生這種情況。
您的靜態變量由類加載器保存,該類加載器包含在應用程序的進程中。 根據谷歌:
Android的一個不尋常的基本特征是應用程序進程的生命周期不是由應用程序本身直接控制的。 相反,它由系統通過系統知道正在運行的應用程序部分的組合,這些事物對用戶的重要性以及系統中可用的總體內存量來確定。
http://developer.android.com/guide/topics/processes/process-lifecycle.html
這對開發人員意味着你不能指望靜態變量無限期地保持初始化。 您需要依賴不同的持久性機制。
我用來保持我的日志記錄框架初始化的一個解決方法是我的所有活動擴展一個基類,我覆蓋onCreate
並檢查初始化並在必要時重新初始化。
我認為官方解決方案是使用onSaveInstanceState(Bundle b)
回調來保留Activity稍后需要的任何內容,然后在b != null
時在onCreate(Bundle b)
重新初始化。
谷歌解釋得最好:
http://developer.android.com/training/basics/activity-lifecycle/recreating.html
只要系統感覺它並且您的應用程序不是頂級(用戶沒有明確地運行它),就會清除靜態引用。 每當您的應用程序被最小化並且操作系統需要更多內存時,它將終止您的應用程序或在固定存儲上將其序列化以供以后使用,但在這兩種情況下都會刪除靜態變量。 此外,每當您的應用獲得Force Close
錯誤時,所有靜態也會被刪除。 根據我的經驗,我發現在Application對象中使用變量總是比靜態變量更好。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.