WebObjects/EOF/使用 EOF/記憶體管理
Java 中的記憶體管理有點像魔術。當與 EOF 結合使用時,您需要注意一些特定問題。在 WebObjects 5.2 中,對 EOF 中的記憶體管理系統進行了重大更改,為 EO 快照提供了對弱引用的支援,而不僅僅依賴於以前的引用計數系統。這為 5.2 及更高版本中相同條件下具有相同應用程式的應用程式提供了大大最佳化的記憶體效能,因為在許多情況下,引用將由 Java 的記憶體系統釋放,而不是由您的 EOEditingContext 保持。
關於 快取和新鮮度 的內容與記憶體管理的內容有很大重疊。您應該閱讀這兩部分內容,以充分了解快取和記憶體管理應如何在您的應用程式設計中發揮作用。
在採取任何措施之前,需要考慮的一個重要問題是您的應用程式是否真的需要比您提供的更多記憶體。預設情況下,Java 虛擬機器為 Java 應用程式提供 64M 的記憶體。對於企業 Web 應用程式來說,這並不是很多記憶體。您可以在啟動時設定 VM 的“mx”標誌來調整它(-Xmx256M 會為 VM 提供 256M 的堆,而不是預設的 64M)。除了在程式碼中漫無目的地尋找之外,分析您的應用程式是真正確定應用程式記憶體狀況的唯一方法。
以下分析工具已被驗證可與 WebObjects 一起使用
- JProfiler(http://www.ej-technologies.com/products/jprofiler/overview.html)對 WebObjects 有明確的支援。它可以自動更新您的 WebObjects 應用程式啟動指令碼,以便在執行時插入自身,允許您遠端連線到應用程式並對其進行分析。
使用 原始行 可以顯著減少您的記憶體使用量(並提高應用程式的效能),但以便利性為代價。與普通 EO 相比,這種方法使用的記憶體更少,對提取限制的支援更好,並且您仍然可以在需要時將它們提升為完整的 EO。檢視 原始行 主題以瞭解更多資訊。
如果您在提取時不小心,可能會引入大量資料集。當使用 EOFetchSpecification 時,請務必指定限制最嚴格的 EOQualifier,該限定符仍能返回您需要的資料。否則,您可能會提取比應用程式使用的更多資料,由於必須從資料庫傳輸資料,從而降低整體應用程式效能。
通常會有代表“列舉型別”的 EO。例如,您可能有一個錯誤跟蹤系統,該系統具有嚴重性實體(具有“嚴重錯誤”、“輕微錯誤”等各個例項),所有錯誤都引用該實體。很容易建立反向引用的關係(即 Bug=>嚴重性,嚴重性=>>Bug)。但是,請注意,如果您從嚴重性建立到 Bug 的引用,那麼在新增 Bug 並正確使用 addObjectToBothSidesOfRelationshipWithKey 方法時,EOF 會在將新 Bug 新增到列表末尾之前,將 Severity 上的整個“錯誤”關係錯誤引入記憶體。這可能會對您的應用程式造成破壞。隨著錯誤數量的增加,新增新錯誤將花費越來越長時間。建議的最佳做法是隻建立 Bug=>嚴重性,而不是建立反向關係。如果您需要執行該提取,可以手動構建 EOFetchSpecification,而不是使用自動方法。
請注意,這是針對 EOF 的已知問題。EOF 可以使用的一種方法是,只有在錯誤關係已經錯誤引入記憶體時,才將物件新增到 Severity 的錯誤關係中。如果陣列尚未錯誤引入,那麼在插入時導致錯誤可能會非常昂貴。當前行為的原因是為了保持在儲存更改之前訪問新提交的 Bug 的能力,透過 Severity 錯誤關係(即從資料庫引入關係不會立即返回新插入的 Bug)。這對於在儲存更改之前維護物件圖的一致性非常重要,但也確實帶來了很高的效能成本。
嚴格來說,在您的 EOEditingContext 上呼叫 dispose() 不是必需的。當 EOEditingContext 不再被引用時,它將自動被釋放。但是,問題是 GC 需要多長時間才能到達您的物件。如果您手動呼叫 dispose(),您不會造成任何傷害,並且會立即減少快照引用計數。如果您的 EC 即將超出範圍,或者您將其設定為 null,那麼誰先到達它可能是一個難題——GC 完成您的 EC 或者您手動呼叫 dispose()。建議您在單個方法中進行長時間執行的程序時釋放您的 EOEditingContext,在該方法中,EOEditingContext 不會在方法結束之前超出範圍。
呼叫 System.gc() 通常是一個不好的做法,會導致應用程式的效能問題。此外,System.gc() 不是命令,而是一個請求,因此,如果您的應用程式依賴於此命令執行,則應該重新評估應用程式的設計,因為您可能會遇到此問題。
呼叫 editingContext.invalidateObject(..) 或 editingContext.invalidateAllObjects() 來解決記憶體問題可能很誘人。請避免這樣做。從長遠來看,你只會自找麻煩。無效化操作非常激進,並且如果另一個 EOEditingContext 中的某個人正在修改你在你的 EOEditingContext 中使之無效的 EO,它將導致異常被丟擲(共享快照將被銷燬,導致在另一個上下文中的 .saveChanges 操作時出現異常)。它只應該在你知道該方法含義的非常受控的情況下使用。
至於使物件無效,這應該避免。我承認我已經有過幾次絕望,並使用了它,但只有後悔和保留。
我個人不喜歡使用 invalidateAllObjects(),因為對我來說,它帶來的問題比解決的問題更多。
我使用它的那幾次,後來都後悔了,因為它最終害了我。最重要的是,你拋棄了人們可能正在編輯的快照,這會造成非常奇怪的問題。這是一種非常笨拙的方法。
人們在 WebObjects 中遇到的最常見的記憶體問題之一是 NSUndoManager。WebObjects 有一個特別酷的功能,它支援 EO 事務的撤銷堆疊。每次你呼叫 saveChanges 時,你都會將一組可撤銷的更改推送到堆疊中。這種行為的缺點是,你最終會對舊的 EO 狀態產生強引用,這在大型 EOF 操作中可能是致命的。
有幾種解決方法。其中一種是透過呼叫以下方法簡單地停用編輯上下文的 NSUndoManager:
editingContext.setUndoManager(null);
據稱將撤銷管理器設定為 null 可能會在某些情況下導致刪除問題。在使用 WebObjects 5.3 進行測試時,無法重現這種情況,但如果你使用 WebObjects 5.2 或更低版本,你應該注意可能會遇到問題。
在這種情況下,建議的替代方法是透過呼叫以下方法停用撤銷事件的註冊:
editingContext.undoManager().disableUndoRegistration();
注意,使用 setLevelsOfUndo() 將撤銷級別設定為 0 不會起作用,因為 0 表示沒有限制!
在 5.2 之前,NSUndoManager 預設情況下具有無限撤銷級別。WO 5.3 的 javadoc 仍然提到以下內容
EOEditingContext 的撤銷支援是任意深度的;你可以重複撤銷一個物件,直到你將其恢復到首次建立或獲取到其編輯上下文時的狀態。即使在儲存之後,你也可以撤銷更改。為了支援此功能,NSUndoManager 可以保留大量資料在記憶體中。
但是,從 5.2 開始,如果你使用會話 defaultEditingContext,WOSession 會將預設撤銷堆疊大小設定為 10,如果使用 WODEFAULTUNDOSTACKLIMIT 引數沒有指定預設大小。
最後,你可以在完成操作後透過呼叫以下方法手動清除你的 NSUndoManager:
editingContext.undoManager().removeAllActions();
如果你使用 NSUndoManager,建議你在執行涉及大量資料的非常大的 saveChanges 操作後呼叫它。
這是對 5.3 中當前行為的描述,一個人可能會透過反編譯和審查整個過程來收集,當然,我永遠不會這樣做或縱容它 - 但是如果你真的做了,你可能會收集到確切的資訊,這些資訊在 5.2 版本說明中得到了很好的記錄(並且似乎符合規範)
從 5.2 開始,它的工作方式是:EODatabase 中的快照具有引用計數。每個獲取 EO 的編輯上下文都會增加引用計數。EC 透過弱引用持有該 EO。當弱引用被回收時,快照引用計數可能會減少(注意是可以,而不是立即就會 - 編輯上下文保留引用佇列,該佇列僅定期處理)。當計數降至零時,資料庫會忘記快照。如果啟用了實體快取,則 EODatabase 會忽略引用計數(或將其保持在“1”作為最小值),並且它不會在只讀場景中消失。如果你修改了該型別的任何實體並在你的 EditingContext 中呼叫 saveChanges,則快取實體的快取將被完全重新整理。(注意:請記住這一點,因為如果你正在快取大量可寫資料,它不會很聰明地更新該快取 - 它會在每次更新時被清除,然後它會在下一次訪問時立即重新載入該實體的整個物件集)
如果在你的編輯上下文中啟用了 retainsAllRegisteredObjects,它不會使用弱引用。在這種情況下,EO 引用計數僅在以下情況下減少:1) 你處置了編輯上下文,或 2) 你忘記或使物件無效。
當你在編輯上下文中修改一個物件時,編輯上下文會保留對該物件的強引用,直到你呼叫 saveChanges(或還原、重置等),此時強引用會被清除,唯一剩下的引用就是之前那樣的弱引用。
如果啟用了撤銷管理器,它會保留對受影響 EO 的強引用,只要撤銷存在。
我確實想知道 EC 是否應該使用軟引用而不是弱引用...... 似乎對那些 EO 的使用者更友好。
如果你使用的是 5.2 之前的 WO,那麼所有弱引用相關內容都不適用,所有內容都是純粹使用快照引用計數完成的 - 它應該表現得像 5.2 中的 retainsAllRegisteredObjects = true。
在 5.2 之前,所有快照都只是由 EOEditingContext 引用計數。如果一個 EO 被故障到你的 EOEditingContext 中,它不會消失,直到 EOEditingContext 本身被處置。因此,如果你在 5.2 之前的 WebObjects 上部署了一個應用程式,你將需要採用定期處置你的 EOEditingContext 並建立一個新的 EOEditingContext 的策略。
根據我們的經驗,EOF 在執行這種批次操作方面並不太好。如果我們有很多事情要做,並且不需要來自 EO 的邏輯,我們會轉到 EOAccess 級別,並編寫一些批次插入語句。你甚至可能想考慮直接使用 JDBC。
好的,也就是說,
- 定期儲存
- 不要保留對已插入物件的引用
- 如果這不起作用,嘗試定期建立一個新的 EC
- 這有點笨拙,但 ec.invalidateAllObjects() 應該也會轉儲所有快照。
一些有用的評論:http://lists.apple.com/archives/webobjects-dev/2004/Sep/msg00225.html http://lists.apple.com/archives/webobjects-dev/2004/Sep/msg00228.html
閱讀 EOF 文件總是很有趣,JavaDocs 僅用於 API,而 EOF 文件包含許多有價值的資訊。 http://developer.apple.com/documentation/WebObjects/Enterprise_Objects/index.html
“EOEditingContexts 使用弱引用來引用註冊到它們的 EOEnterpriseObjects。... EOEditingContexts 透過強引用保留所有插入、刪除或更新的物件。這些強引用由 EOEditingContext 方法 saveChanges 清除...” - http://developer.apple.com/documentation/WebObjects/Enterprise_Objects/Managing/chapter_7_section_9.html
關於定期建立一個新的 EC,我會直接跳到這個選項(根據我的經驗)。我做過幾個類似的任務,發現確保應用程式不會膨脹的最佳方法是定期放棄 EC 並建立一個新的 EC。這似乎是不必要的...... 但它有效。
我的猜測是記憶體增加是由於快照造成的,據我所知,快照永遠不會消失,而且在我看來,這不算是一個錯誤。
為了處理我遇到的一個永遠匯入問題,我不想使用笨拙的“新 EC”方案,所以我編寫了這個方法,它可以減少快照數量
public static void forgetObjectsAndKeypaths(EOEditingContext ec, NSArray eos, NSArray keypaths) {
Enumeration eoEnum = eos.objectEnumerator();
while (eoEnum.hasMoreElements()) {
EOGenericRecord eo = (EOGenericRecord) eoEnum.nextElement();
Enumeration keypathEnum = keypaths.objectEnumerator();
while (keypathEnum.hasMoreElements()) {
String keypath = (String) keypathEnum.nextElement();
EOFaulting faulting = (EOFaulting) eo.valueForKeyPath(keypath);
if (faulting != null && !faulting.isFault()) {
if (eo.isToManyKey(keypath)) {
NSArray relEos = (NSArray) eo.valueForKeyPath(keypath);
EOHelper.forgetObjects(ec, relEos);
} else {
ec.forgetObject((EOCustomObject) eo.valueForKeyPath(keypath));
}
}
}
ec.forgetObject(eo);
}
}
你可以像這樣使用它
EOHelper.forgetObjectsAndKeypaths(ec, arrayOfEOsToForget, new NSArray(new String[]{"rel1", "rel2"});
當然,陣列中所有的EO必須是同一個實體,keypaths才能工作!