1. Session Memory DUMP
ABAP程序在运行过程中,每一个线程都会分配一定额度的内存,我们通常可以称之为“Session Memory”。SAP系统GUI上默认最大可以开6个窗口,一个窗口其实就是一个ABAP session。
Session中的程序在运行时,如果超出最大的内存限额,程序便会发生Session Memory的DUMP:
TSV_TNEW_PAGE_ALLOC_FAILED,也即分配新的内存额度失败。
解决这个问题的方式有两种,一种是让Basis Team调整配置文件,增加一个session中最大的内存分配;另一种就是分析代码,看看ABAP程序是否有优化的空间。
事实证明,大部分的session memory dump,其实都是由于在程序中内存使用不合理造成的。今天我们来主要分析一下由于内表使用不合理造成的内存溢出。
2. Lazy Copy Strategy (Copy-on-write)
Lazy Copy 是ABAP中的对内表分配内存时的一种策略,具体解释可看下面代码:
DATA lt_data1 TYPE TABLE OF acdoca.
DATA lt_data2 TYPE TABLE OF acdoca.
DATA ls_data TYPE acdoca.
lt_data2 = lt_data1. " no copy occurred, they use same memory object
APPEND ls_data TO lt_data2. " lt_data2 is changed, a new memory object is create, now the memory consumption is double
当仅仅是一个内表的变量指向指向另一个内表变量时 lt_data2 = lt_data1 ,其实并不会增加任何内存的消耗,因为它们会共用一个内存对象。
但当lt_data2中的内容发生改变时,系统会立即开辟一个新的内存对象给到它。在这个例子中,内存的消耗也变会翻番。
因此,在使用内表时,我们要避免使用多余的变量,尽可能保证一个内表仅有一个内存对象,当内表使用完毕后,也尽量清空,把内存释放掉。
3. 举个例子
我现在内表 lt_acdoca1 中准备了102400条数据,然后将 lt_acdoca1 赋值给 lt_acdoca2 和 lt_acdoca3.
我们可以看到,虽然是3个独立的变量,但它们此时存储的数据是一样的。因此它们也是共用同一个内存对象,此对象消耗了792576144 Byte的内存,也即755MB。
此时,当我们改变内表 lt_acdoca1 的值时,可以看到一个新的内存对象被创建出来,此时当前线程的内存中这两个变量的内存消耗为 755 + 755,也即仅1.5GB。 翻番了!
明白了么?对于内表使用时,特别是内表中存储的数据量非常大时,要避免多个变量同时都在存储类似(相同)的数据,因为这种无意义的行为,可能会直接消耗掉session中的所有内存。
4. Session Memory的trace工具
ABAP中的debuger中,内置了session memory的分析工具,示例见下图。当然,也可以单独使用transaction:S_MEMORY_INSPECTOR 来进行trace。有关具体的trace方式,可以参考此文档。
5. 示例分析程序
下面给出了我在本文中,为说明问题所写的一个小程序,感兴趣的同学可以自己debug下。
CLASS lcl_acdoca DEFINITION FINAL CREATE PRIVATE.
PUBLIC SECTION.
TYPES tt_acdoca TYPE TABLE OF acdoca.
CLASS-METHODS create
RETURNING VALUE(ro_instance) TYPE REF TO lcl_acdoca.
METHODS set_value
IMPORTING VALUE(it_acdoca) TYPE tt_acdoca.
METHODS set_default_company_code
IMPORTING iv_bukrs TYPE bukrs.
PRIVATE SECTION.
DATA mt_acdoca TYPE tt_acdoca.
ENDCLASS.
CLASS lcl_acdoca IMPLEMENTATION.
METHOD create.
ro_instance = NEW #( ).
ENDMETHOD.
METHOD set_value.
mt_acdoca = it_acdoca.
ENDMETHOD.
METHOD set_default_company_code.
DATA(lt_acdoca) = mt_acdoca.
LOOP AT mt_acdoca ASSIGNING FIELD-SYMBOL(<ls_acdoca>).
<ls_acdoca>-rbukrs = iv_bukrs.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
**********************************************************************
* Value semantic
**********************************************************************
START-OF-SELECTION.
DATA lt_acdoca1 TYPE TABLE OF acdoca.
DATA lt_acdoca2 TYPE TABLE OF acdoca.
DATA lt_acdoca3 TYPE TABLE OF acdoca.
SELECT * FROM acdoca INTO TABLE lt_acdoca1 UP TO 100 ROWS.
LOOP AT lt_acdoca1 REFERENCE INTO DATA(lr_acdoca1).
lr_acdoca1->gjahr = 2020.
ENDLOOP.
DO 10 TIMES.
APPEND LINES OF lt_acdoca1 TO lt_acdoca1.
ENDDO.
* Value reference
lt_acdoca2 = lt_acdoca1.
lt_acdoca3 = lt_acdoca1.
* changing
* lt_acdoca2[ 1 ]-gjahr = 2021.
lt_acdoca1[ 1 ]-gjahr = 1900.
lt_acdoca3[ 1 ]-gjahr = 2022.
**********************************************************************
* Reference semantic
**********************************************************************
DATA lo_acdoca1 TYPE REF TO lcl_acdoca.
DATA lo_acdoca2 TYPE REF TO lcl_acdoca.
DATA lo_acdoca3 TYPE REF TO lcl_acdoca.
lo_acdoca1 = lcl_acdoca=>create( ).
lo_acdoca1->set_value( lt_acdoca1 ).
CLEAR lt_acdoca1.
lo_acdoca1->set_default_company_code( 'CFP1' ).
lo_acdoca2 = lo_acdoca1.
lo_acdoca3 = lo_acdoca1.
lo_acdoca3->set_default_company_code( 'CFP2' ).
cl_demo_output=>display( 'done').
本博客专注于技术分享,干货满满,持续更新。
欢迎关注❤️、点赞👍、转发📣!