J2ObjC 内存模型

本文档介绍了使用 J2ObjC 转换后代码管理内存的方式,以及程序在访问共享内存时的行为方式。

内存管理

J2ObjC 的目标之一是生成可无缝集成到 Objective-C 的引用计数环境的翻译版代码。这样一来,就可以从原生编写的 Objective-C 中轻松使用转换后的 Java 代码,因为在 Java 和 Objective-C 环境之间传递的对象不会尴尬地转移所有权。

由于 Java 使用垃圾回收进行内存管理,因此 Java 代码不包含其对象的显式内存管理。因此,J2ObjC 必须适当地插入引用计数调用,以确保在正确的时间取消分配对象。我们已经确定了下面这些性能出色且实用的规则:

  • 所有对象都将至少存在当前自动释放池的时长。
    • 这条通用规则允许我们跳过许多本来必需的保留和释放。
  • 局部变量不会保留。
    • 没有引用计数局部变量读取或写入的调用次数。
  • 系统会保留字段。
    • 字段调用的分配将保留在新值上,并基于旧值自动释放。
  • 新对象会立即自动释放。(除非立即分配到字段)

参考周期

当对象直接或通过其字段间接引用自身时,就存在引用循环。Java 的垃圾回收可以清理引用周期,但会在 Objective-C 的引用计数环境中泄漏内存。没有可以自动阻止引用周期的发生;但是,我们提供了一个 Cycle Finder 工具,可以自动检测周期。以下是解决引用循环的一些常见方法:

  • 添加 @Weak@WeakOuter 注解以削弱其中一个引用。
  • 向其中一个对象添加 cleanup() 方法,以将某些字段设置为 null。在舍弃对象之前,请先调用 cleanup()
  • 重新设计代码以避免完全创建引用循环。

共通记忆

在多线程程序中,某些数据可由多个线程共享。Java 提供了一些工具,以允许以线程安全的方式访问共享数据。本部分介绍 J2ObjC 对访问共享数据的支持。

已同步

J2ObjC 会将 synchronized 关键字直接映射到 Objective-C @synchronized

原子性

Java 可保证除 longdouble 以外的所有类型的加载和存储的原子性。请参阅 JLS-17.7。除了 volatile 字段(如下所述)之外,J2ObjC 没有提供任何特殊处理来确保原子加载和存储。这意味着:

  • 由于所有 iOS 平台均为 32 位或 64 位,因此除 longdouble 以外的基元类型的加载和存储在 32 位设备上都是原子化的,而在 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 用户设置其他约束。