J2ObjC 記憶體模型

本文件說明如何在經過 J2ObjC 轉譯的程式碼中管理記憶體,以及程式存取共用記憶體時的行為。

記憶體管理

J2ObjC 的目標之一,是產生經過翻譯的程式碼,可以完美整合至 Objective-C 的參考計數環境。如此一來,經過翻譯的 Java 程式碼就能從原生編寫的 Objective-C 中輕鬆使用,因為在 Java 和 Objective-C 環境之間傳遞的物件並不會造成尷尬的移轉。

由於 Java 會使用垃圾收集進行記憶體管理,因此 Java 程式碼並未包含其物件的明確記憶體管理。因此,J2ObjC 必須適當插入參照計數呼叫,以確保物件會在適當時機釋放。我們發現以下規則 這兩項均兼具性能和實用性:

  • 所有物件至少都會保留到目前自動釋出集區的時間長度。
    • 這項通則可讓我們略過多項必要保留和釋出作業。
  • 本機變數不會保留。
    • 本機變數讀取或寫入沒有參照計數呼叫。
  • 欄位則會保留。
    • 指派的欄位呼叫會保留新值,並依據舊值自動釋放。
  • 系統會立即自動釋放新物件。(除非立即指派給欄位)

參照週期

當物件直接或間接透過其欄位參照自己時,就會產生參照週期。Java 的垃圾收集可以清理參照週期,但會在 Objective-C 的參考計數環境中流失記憶體。目前沒有自動化的方法可防止參照週期發生;不過,我們提供「Cycle Finder」工具,可自動偵測循環。以下是修正參考週期的常見方法:

  • 新增 @Weak@WeakOuter 註解,以削弱其中一個參照。
  • cleanup() 方法新增至其中一個物件,並將部分欄位設為空值。請先呼叫 cleanup(),再捨棄物件。
  • 重新設計程式碼,避免完全建立參照週期。

共用回憶集錦

在多執行緒程式中,部分資料可由多個執行緒共用。Java 提供多種工具,讓您可以在執行緒安全的情況下存取共用資料。本節說明 J2ObjC 對存取共用資料的支援。

已同步處理

J2ObjC 將 synchronized 關鍵字直接對應至 Objective-C @synchronized

完整性

Java 會保證所有類型的載入和儲存作業的單元性,但 longdouble 除外。請參閱 JLS-17.7。儘管 volatile 欄位 (如下所述) 除外 (如下所述),J2ObjC 並未提供任何特殊處理方式,可確保載入和儲存不可分割。這意味著:

  • 由於所有 iOS 平台都是 32 或 64 位元版本,因此在 32 位元裝置上,longdouble 以外的載入和儲存都是不可分割的,而且在 64 位元系統上都是不可分割的形式。
  • 在 J2ObjC 中,物件類型的載入與儲存並非不可分割。
    • 個別更新參考計數所費不貲。
    • 只要將物件欄位宣告 volatile,即可讓欄位不可分割。(詳情請參閱下方)

易變性場

針對 volatile 欄位,Java 提供不可分割性和半一致性排序 (JLS-8.3.1.4),可用於同步處理。J2ObjC 為所有 volatile 欄位提供與 Java 相同的保證。J2ObjC 針對 volatile 欄位使用下列機制:

  • 原始類型會對應至 c11 原子類型。
    • 例如 volatile int -> _Atomic(jint)
  • 物件欄位受到 pthread 互斥鎖保護。(由於優先順序反轉,因此無法使用旋轉鎖定)
    • 需要雙向排除,才能避免參照計數發生競爭狀況。
    • 實作方式與 Objective-C 原子屬性非常類似。

原子類型

Java 在 java.util.concurrent.atomic 套件中提供多種不可分割類型。這些程式在 J2ObjC 中受到完整支援,並具有自訂的實作。

最終欄位

Java 可保證執行緒能在共用物件時,看到物件的最終欄位初始化的值,而不需要進行任何同步處理。(JSL-17.5) 然而,由於 J2ObjC 不支援不可變動物件類型不可部分存取 (請參閱上文),因此無法安全地在未同步處理的情況下共用物件。因此,藉由省略最終欄位所需的記憶體圍欄,J2ObjC 使用者就不會出現其他限制。