本文档介绍了使用 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 可保证除 long
和 double
以外的所有类型的加载和存储的原子性。请参阅 JLS-17.7。除了 volatile
字段(如下所述)之外,J2ObjC 没有提供任何特殊处理来确保原子加载和存储。这意味着:
- 由于所有 iOS 平台均为 32 位或 64 位,因此除
long
和double
以外的基元类型的加载和存储在 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 用户设置其他约束。