这是Jerry 2020年的第54篇文章,也是汪子熙公众号总共第237篇原创文章。
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:
DATA: lo_person TYPE REF TO lcl_person.
CREATE OBJECT lo_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
https://blogs.sap.com/2016/07/02/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这种编程语言里,还存在另外两种引用:软引用和虚引用。感谢阅读。
ABAP专题
Jerry的ABAP, Java和JavaScript乱炖
ABAP开发人员未来应该学些什么
Jerry 2017年的五一小长假:8种经典排序算法的ABAP实现
Jerry的ABAP原创技术文章合集
300行ABAP代码实现一个最简单的区块链原型
使用Java+SAP云平台+SAP Cloud Connector调用ABAP On-Premise系统里的函数
在SAP云平台的CloudFoundry环境下消费ABAP On-Premise OData服务
ABAP vs Java, 蛙泳 vs 自由泳
聊聊C语言和ABAP
动手使用ABAP Channel开发一些小工具,提升日常工作效率
我用ABAP做过的那些无聊的事情
不喜欢SAP GUI?那试试用Eclipse进行ABAP开发吧
使用Visual Studio Code编写和激活ABAP代码
你的ABAP程序给佛祖开过光么?来试试Jerry这个小技巧
在SAP云平台ABAP编程环境上编写第一段ABAP程序
SAP官方发布的ABAP编程规范
ABAP Code Inspector那些隐藏的功能,您都知道吗?
还在用ABAP进行SAP产品的二次开发?来了解下这种全新的二次开发理念吧
ABAP Netweaver体内的那些寄生式编程语言
从SAP社区上的一篇博客开始,聊聊SAP产品命名背后的那份情怀
云端的ABAP Restful服务开发
如何在SAP云平台ABAP编程环境里把CDS view暴露成OData服务
使用abapGit在ABAP On-Premises系统和SAP云平台ABAP环境之间进行代码传输
30分钟用Restful ABAP Programming模型开发一个支持增删改查的Fiori应用
Jerry带您了解Restful ABAP Programming模型系列之二:Action和Validation的实现
Jerry带您了解Restful ABAP Programming模型系列之三:云端ABAP应用调试
SAP云平台上的ABAP编程环境里如何消费第三方服务
ABAP开发者上云的时候到了 - 现在大家可以免费使用SAP云平台ABAP环境的试用版了
学而不思则罔 - SAP云平台ABAP编程环境的由来和适用场景
SAP云平台里的三叉戟应用
如何基于Restful ABAP Programming模型开发并部署一个支持增删改查的Fiori应用
SAP 2019 TechEd Key Note解读:云时代下SAP从业人员如何做二次开发?
有哪些ABAP关键字和语法,到了ABAP云环境上就没办法用了?
ABAP开发环境终于支持以驼峰命名法自动格式化ABAP变量名了
利用ABAP 740的新关键字REDUCE完成一个实际工作任务
一段让人瑟瑟发抖的ABAP代码
昨日万圣节ABAP怪兽级代码谜团,公布答案啦
介绍一种在ABAP内核态进行内表高效拷贝的方法
使用SAP Cloud Application Programming模型开发OData的一个实际例子
当ABAP遇见普罗米修斯
使用ABAP绘制可伸缩矢量图
ABAP开发环境语法高亮的那些事儿
SAP错误消息调试之七种武器:让所有的错误消息都能被定位
使用ABAP操作Excel的几种方法
SAP GUI里的收藏夹事务码管理工具
SAP GUI和Windows注册表
有了Debug权限就能干坏事?小心了,你的一举一动尽在系统监控中
ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX这些东东是什么鬼
实现ABAP条件断点的三种方式
使用SAT跟踪监控从浏览器打开的SAP应用的性能和调用栈
一个13年ABAP老兵的建议:了解这些基础知识,对ABAP开发有百利而无一害
SAP ABAP Netweaver容器化, 不可能完成的任务吗?
SAP产品增强技术回顾
SAP API开发方法大全
浅谈Java和SAP ABAP的静态代理和动态代理,以及ABAP面向切面编程的尝试
SAP ABAP应用服务器的HTTP响应状态码(Status Code)
SAP ABAP里存在Java List这种集合工具类么?CL_OBJECT_COLLECTION了解一下
ABAP面试题系列:写一组会出现死锁(Deadlock)的ABAP程序
SAP ABAP Netweaver服务器的标准登录方式讲解
SAP ABAP关键字语法图和ABAP代码自动生成工具Code Composer
SAP ABAP SM50的另类用途 - ABAP工作进程对数据库表读取操作的检测
关于SAP ABAP字符变量和字符串变量字符个数的一个知识点,和一个血案
SAP ABAP一组关键字 IS BOUND, IS NOT INITIAL和IS ASSIGNED的用法辨析