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:

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

我給這個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這種編程語言裏,還存在另外兩種引用:軟引用和虛引用。感謝閱讀。

要獲取更多Jerry的原創文章,請關注公衆號"汪子熙":

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章