SAP ABAP 和 Java 里的弱引用 (WeakReference) 和软引用 (SoftReference)
Jerry 前一篇文章 SAP ABAP一组关键字 IS BOUND, IS NOT INITIAL和IS ASSIGNED的用法辨析 介绍了在 ABAP 里判断引用变量是否包含了一个有效引用的关键字:IS BOUND.
本文则从 ABAP 和 Java 编程语言里不同的引用类型这个角度来继续引用这个话题的讨论。
不知道大家留意过这个 ABAP 抽象类 CL_ABAP_REFERENCE 吗?这个抽象类只有一个 GET 方法,返回一个对象引用。
它的两个子类 CL_ABAP_SOFT_REFERENCE 和 CL_ABAP_WEAK_REFERENCE,分别实现了抽象类的 GET 方法,不过均在 ABAP Kernel 里实现的,对 ABAP 应用开发人员来说,看不见源代码,是一个黑盒子。
怎么使用这个类呢?还是查看 SAP 帮助文档:
An object in the system class CL_ABAP_WEAK_REFERENCE represents a weak reference to an object in a class. Unlike regular object references, a weak reference is ignored during execution of the garbage collector. This means that a weak reference does not prevent the referenced object from being deleted when the garbage collector is executed.
CL_ABAP_WEAK_REFERENCE 类的实例, 代表指向一个对象实例的弱应用。从字面上理解,既然存在弱引用,自然也存在其对立面的强应用。假设有一个 ABAP 类 lcl_person:
上述代码定义了一个指向 lcl_person 对象实例的强引用变量,名为 lo_person. 当垃圾回收器工作的时候,只要 lcl_person 对象实例的强引用 lo_person 还有效(即没有调用 CLEAR, 或者没有被重新赋值指向其他的对象实例), 则 lo_person 对象实例所占据的内存区域不会被 ABAP 垃圾回收器释放。换句话说,lcl_person 对象实例如果至少存在一个指向它的强引用,则在任何情况下,其内存区域都不会被 ABAP 垃圾回收器回收。
而弱引用,在垃圾回收阶段会直接被忽略。这就意味着,在 ABAP 垃圾回收器开始工作的时候,如果一个对象实例并未有任何强引用指向它,此时无论有无弱引用指向它,该对象实例都无法逃脱被回收的命运。
看个具体的例子。
这个 30 行的 ABAP 报表,实现了一个简单的 LCL_PERSON 类。第 17 行创建了一个该类的实例,该实例的强引用存储在引用变量 lo_person 里。
第 18 行创建了一个包裹 LCL_PERSON 对象实例的弱引用 lo_weak. 调用弱引用 lo_weak 的 get 方法,在两种不同的情况下有两种不同的返回结果:
(1) 如果第 17 行创建的 lcl_person 对象实例已经被垃圾回收器回收了,则 get 返回一个空引用;(2) 如果 lcl_person 对象实例没有被回收,则返回指向其的引用。
需要本文源代码的朋友,可以在我的 SAP 社区博客里找到:
Weak reference in ABAP and Java
我给这个 ABAP 程序指定了两个输入参数,clear 和 gc,分别控制是否清除强引用变量 lo_person,和是否用代码调用 ABAP 垃圾回收器。
如果执行程序时传入的参数 clear 置为 true,则调用 CLEAR: lo_person. 根据 ABAP 帮助文档,CLEAR 施加在引用变量 lo_person 上,执行后 lo_person 指向空引用(null reference).
另一个参数 gc 置为 true,则用代码的方式启动 ABAP 垃圾回收器:cl_abap_memory_utilities=>do_garbage_collection.
这两个开关的开闭情况,构成了 4 种不同的排列组合。在这四种排列组合下,由弱引用 lo_weak 指向的对象实例,是否会被 ABAP 垃圾回收器回收?结果如下表:
由此可见,弱引用指向的对象实例,在 ABAP 垃圾回收器启动之后,如果没有再被至少一个强引用变量所指向,则会被垃圾回收器回收。
使用事务码 s_memory_inspector,在垃圾回收器启动之后制作一个内存快照(Memory Snapshot),发现在上表第一种排列组合下,lcl_person 对象实例已经被回收了: No memory objects found.
而其他三种排列组合下,lcl_person 都逃脱了被垃圾回收器回收的命运:
Java 里也有对应 CL_ABAP_WEAK_REFERENCE 的弱引用实现:java.lang.ref.WeakReference.
Jerry 本文的 ABAP 程序,翻译成 Java 代码如下:
因为这两种编程语言的弱引用,工作原理完全一致,所以上面 Java 版本的例子 Jerry 就不赘述了。
上面第 25 行代码里将强引用变量 jerry 指向的对象置为 null,第 26 行启动 Java 垃圾回收器,于是第 27 行调用弱引用变量 get 方法得到的结果是:null.
那么这种弱引用有什么使用场景?
最好的学习方式就是对 CL_ABAP_WEAK_REFERENCE 执行 Where-used 操作,来查找有哪些 SAP 标准应用使用到了这个类。
在下图 Jerry 使用的 SAP CRM 系统里,弱引用的使用场合还不少。
这 500 多处使用场景里,最典型的就是缓存(Cache)的实现场景。下图这个 CRM 增强工具 Application Enhancement Tool(简称 AET)工厂类的方法 GGET_DATA_TYPE_HANDLER, 根据两个输入参数,字段数据类型和字段行为类型,返回对应的处理器实例(handler). 这些处理器实例化时需要从若干张数据库表里读取数据并保存在内存里,因此初始化过程需要花费一定的时间。为了避免这个方法每次被调用时都花费时间重复地访问数据库表,创建新的实例,该工厂类引入了一个缓存机制, 即下图第 21 行的内表 gt_type_handler_cache. 每次 GET 方法被调用时,先去该内表里查看是否存在对应的处理器实例。如果有,直接返回,省去了费时的处理器实例化工作。
看这个缓存的设计,行项目的类型结构里,handler 的类型并不是具体的 IF_AXT_DATATYPE_HANDLER, 而是一个弱引用。
技术上说,上图第七行改成:
handler TYPE REF TO IF_AXT_DATATYPE_HANDLER,也是一种正确的设计,而且也正是绝大多数 ABAP 应用人员最常规的缓存设计方案。为讨论方便,我将这种大家都常用的方案称为方案 B,而上图 AET 工厂类采用弱应用指向处理器实例的方案称为方案 A.
方案 A 的优点是,进可攻退可守。
进可攻,即如果 ABAP 垃圾回收器没有调用,并且至少存在一个指向某处理器实例的强引用,此时两种方案运行时没有大的差异,唯一的细微区别之处就是方案 A 在读缓存内表命中,拿到 buffer 里存放的弱引用之后,再调用弱引用的 get 方法,拿到处理器实例并返回。而方案 B 读缓存内表命中后,buffer 里存在的就是处理器实例本身,直接返回给调用端即可。
退可守,就是一旦程序里再也没有指向该处理器实例的强引用,并且 ABAP 垃圾回收器开始工作,那么弱引用指向的处理器实例会被销毁,释放了其消耗的内存。下次如果 GET 方法再次调用,会从数据库里重新加载数据,初始化处理器实例(下图红色区域), 并重新创建弱引用(下图蓝色区域)。
一言以蔽之,弱引用 CL_ABAP_WEAK_REFERENCE 最适合用于描述有一定用处,但不是必需驻留在内存里的对象实例。因此在 SAP CRM 很多框架代码的缓存设计上有着广泛的应用。
其实 ABAP 除了强引用和弱引用之外,还存在第三种类型的引用:软引用(CL_ABAP_SOFT_REFERENCE).
同弱引用相比,软引用指向的对象,只有当没有被任何强引用指向,且垃圾回收器运行时,系统内存不足时才会被销毁。系统可用内存降低到百分之多少才算是“不足”呢?软引用并未在 ABAP 里实现,所以我们也无法继续讨论下去。
Java 里除了弱引用和软引用之外,还存在 PhantomReference(虚引用).
顾名思义,Java 里的虚引用就是"形同虚设",因为通过虚引用的 get 方法,获取到的结果永远为 null.
在有的中文资料里,PhantomReference 因其这种表现行为,又被翻译成"幻引用","幽灵引用"。这个名字让我想起了《星际争霸》里人族的幽灵战机 Wraith.
虚引用主要用来跟踪对象实例被垃圾回收器回收的活动,必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现还有虚引用指向这个对象实例,就会在回收该实例的内存之前,把这个虚引用加入到与之关联的引用队列中。
因为 ABAP 里根本没有虚引用,所以 Jerry 也不展开叙述了。
希望本文能让大家对 ABAP 里两种引用:强引用和弱引用的设计和作用有一个全面了解,同时能知道像 Java 这种编程语言里,还存在另外两种引用:软引用和虚引用。
感谢阅读。
版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/4e6299d13e8934d92354174e9】。文章转载请联系作者。
评论