Skip to Content
Author's profile photo Horst Keller

ABAP Geek 16 – Staying Alive

This one might be interesting for you, if you search for memory leaks.

Instances of classes or simply “objects” are created by CREATE OBJECT. Anonymous data objects are created by CREATE DATA. After creation, references (object references and data references) contained in reference variables are pointing to these objects. We all know that the objects are kept alive by these references and that they are deleted by the garbage collector if they are not referenced any more. But what kind of references are keeping objects alive? Let’s have look.

We start with the memory where objects are stored. In the working memory of an ABAP program (the well known roll area of an internal session) we can distinguish two further memory areas:

  • The heap – the heap is the memory where all objects created by the CREATE statements, i.e. instances of classes and anonymous data objects, are stored.
  • The stack – the stack is the memory where the (named) data objects of programs and procedures are stacked. The memory occupied by the local data of a procedure (including methods) is popped from the stack, when the execution of the procedure has finished.

Now it is easy to distinguish two types of reference variables:

  • Heap references -heap references point to objects and parts of objects in the heap. Heap references are created when new objects are created with CREATE OBJECT or CREATE DATA. But you can also create heap references using the statement GET REFERENCE or the addition REFERENCE INTO for data objects or part of data objects in the heap.
  • Stack references – stack references. point to objects and parts of objects in the stack. Stack references can be created only using the statement GET REFERENCE or the addition REFERENCE INTO for data objects or part of data objects in the stack.

An object reference is always pointing to an instance of a class in the heap and therefore an object reference is always a heap reference.

A data reference is a heap reference, if it points to an instance attribute or a part of an instance attribut of an object in the heap, or if it points to an anonymous data object or a part of an anonymous data object in the heap. A data reference is a stack reference, if it points to a data object or a part of a data object on the stack. A part of an instance attribute or of a data object can be a component of a structure, a line of an internal table, or a section specified by an offset and length.

And now about keeping objects alive:

  • Heap references pointing to objects or parts of objects in the heap keep the objects alive.
  • Stack references pointing to (data) objects or parts of (data) objects in the stack do not keep the objects alive.

With other words and for example: A data reference pointing to a line of an internal table that is an instance attribute of an object keeps the object alive, even if you have cleared the object reference variable!

CLASS cls DEFINITION.
PUBLIC SECTION.
DATA itab TYPE TABLE OF i.
METHODS constructor.
ENDCLASS.

CLASS cls IMPLEMENTATION.
METHOD constructor.
APPEND 1 TO itab.
ENDMETHOD.
ENDCLASS.

DATA: oref TYPE REF TO cls,
dref TYPE REF TO data.

START-OF-SELECTION.
CREATE OBJECT oref.
READ TABLE oref->itab INDEX 1 REFERENCE INTO dref.
CLEAR oref.
WAIT UP TO 1 SECONDS.
IF dref IS BOUND.
MESSAGE ‘Alive!’ TYPE ‘I’.
ENDIF.

And even more: Field symbols pointing to objects or part of objects in the heap (if you use the statement ASSIGN or the addition ASSIGNING for heap objects or part of heap objects) also keep the objects alive!

Last but not least a word about validity: Since heap references keep objects alive, as a rule they are always valid (IS BOUND is true). But ABAP wouldn’t be ABAP if there were not one exception from that rule: Internal tables are dynamic data objects with an own memory management that is independent from CREATE and garbage collection. A data reference pointing to a line of an internal table in the heap is a heap reference and keeps the table and its surrounding object alive. Nevertheless, it can become invalid (IS BOUND is false) if the internal table line is deleted from the table. For strings you could expect the same, but ABAP does not allow you to point to sections of strings with references or field symbols. Stack references can always become invalid of course, if the referenced objects are popped from the stack.

Assigned Tags

      12 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Hermann Gahm
      Hermann Gahm
      Hi Horst,

      thanks for sharing this. Would like to see more of such deep ABAP topics from you.

      Kind regards,

      Hermann

      Author's profile photo Clemens Li
      Clemens Li
      Thank you Horst,

      this is the first time I read such a good explanation of heap and stack concepts within ABAP and ABAP objects.

      Yes, please, more like this!

      Regards,

      Clemens

      Author's profile photo Martin Ceronio
      Martin Ceronio

      Hello Horst,

      There is a very interesting question on StackOverflow where the user asks whether the string representing an object reference like the one you see in the debugger, e.g. "{O:9*\PROGRAM=ZAVG_DELETE_THIS\CLASS=LCL_SMTH}", can be used to obtain an actual reference.

      Firstly, because the debugger is able to do this, and secondly because it seems the number after the "O:" points to a reference on the heap, I assume this must be possible.

      (As indicated in the question, the user is satisfied with having a variable name reference to the instance, but it is nonetheless a very interesting question).

      Regards,

      Martin

      Author's profile photo Former Member
      Former Member

      Thanks Horst for your explanation!

      But unfortunately ABAP cannot handle freed data properly. For example this code produce dump SYSTEM_DATA_ALREADY_FREE:

      REPORT z_garbage_collector_test.
      
      DATA gst_ref  TYPE SORTED TABLE OF REF TO data
                                WITH NON-UNIQUE DEFAULT KEY.
      
      START-OF-SELECTION.
        PERFORM start.
        PERFORM start.
      
      FORM start.
        DATA lv_data      TYPE c.
        DATA lr_ref       LIKE LINE OF gst_ref.
      
        GET REFERENCE OF lv_data INTO lr_ref.
      
        READ TABLE gst_ref TRANSPORTING NO FIELDS
                           WITH TABLE KEY table_line = lr_ref.
        IF sy-subrc <> 0.
          INSERT lr_ref INTO TABLE gst_ref.
        ENDIF.
      ENDFORM.

      After adding the first reference of local variable to the global table with key, when form start executed for the second time the first reference is freed and the key of table become illegal.
      The programmer cannot do anything in this situation and the dump occurs.

      If working with table without using the key everything is fine.

      I don't know workarounds except deleting lines with freed data before reading the table.
      Can anybody help me?

      Author's profile photo Horst Keller
      Horst Keller
      Blog Post Author

      Iurii,

      But that concerns the last sentence of the blog,

      "Stack references can always become invalid of course, if the referenced objects are popped from the stack."

      The reference is not freed (no Garbage Collection), but it is a stack reference that pointed to lv_data during the execution of the procedure. In the moment the procedure is left, it is popped from the stack and the reference in the table line becomes invalid.

      This is rather a programming error than a problem of the language.

      A simplified form of the same "programming error":

      START-OF-SELECTION.
        DATA ref TYPE REF TO i.
        PERFORM start CHANGING ref.
        WRITE ref->*.
      
      
      FORM start CHANGING p TYPE REF TO i.
        DATA lv_data      TYPE i.
        GET REFERENCE OF lv_data INTO p.
      ENDFORM.

       
      Best

      Horst

      Author's profile photo Former Member
      Former Member

      Horst, I understand you, but disagree that it is a "programming error".
      I mean that I don't want access lines of table with illegal references but cannot do it fast using a key of a table.

      For example, this code don't work:

      DATA gst_ref  TYPE STANDARD TABLE OF REF TO data
                             WITH NON-UNIQUE SORTED KEY ref
                             COMPONENTS table_line.
      
      DELETE gst_ref " Syntax error: WHERE condition cannot be optimized with the specified secondary key "REF" of type "SORTED".
              USING KEY ref
              WHERE table_line IS NOT BOUND.

      I even cannot read this table using a key if there is a line with "FREED STACK:{A:1*\TYPE=%_T00006S00000000O0000000291}":

      READ TABLE gst_ref " Dump: SYSTEM_DATA_ALREADY_FREE
                         TRANSPORTING NO FIELDS
                         WITH TABLE KEY ref
                         COMPONENTS table_line = REF #( gst_ref ).

      I can only delete illegal lines this way and read table using a key:

        LOOP AT gst_ref ASSIGNING FIELD-SYMBOL(<ls_ref>)
                        USING KEY ref.
          IF <ls_ref> IS NOT BOUND.
            DELETE gst_ref USING KEY loop_key.
          ENDIF.
        ENDLOOP.

      But it isn't fast. I have to loop at every row and if the table is big it is time-consuming.

      Author's profile photo Horst Keller
      Horst Keller
      Blog Post Author

      Hi Iurii,

       
      First,

      DELETE gst_ref " Syntax error: WHERE condition cannot be optimized with the specified secondary key "REF" of type "SORTED".
              USING KEY ref
              WHERE table_line IS NOT BOUND.

      Well, yes. As explained in the respective documentation, the key access cannot be optimized and therefore, using USING KEY is not possible. Without USING KEY it works.

       
      Second,

      I still can’t see the rationale of putting references to local data objects into a global internal table. Would be the same for putting references to local data objects of a method into an internal table that is an attribute of the class. It is clear that such a reference becomes invalid when leaving the procedure, why storing it in a non-local context? Maybe you want to use it in a procedure called from the local context? Then it would be better to tear down the full table when leaving the context.

       
      Third,

      I don’t see your use case but replacing

        DATA lv_data      TYPE c.
      

      with

        STATICS lv_data      TYPE c.
      

      in your program above prevents the exception.

      Horst

       

      Author's profile photo Former Member
      Former Member

      Thank you, Horst!

      As explained in the respective documentation

      Yes, the documentation is almost perfect. I really love it and you are doing a great work.
      But I cannot use index when there is NOT clause and this code works slowly:

      DATA gst_ref  TYPE SORTED TABLE OF REF TO data
              WITH NON-UNIQUE KEY table_line.
      
      DELETE gst_ref WHERE table_line IS NOT BOUND.

       

      replacing

      STATICS lv_data      TYPE c.
      

      Yes, I already use this workaround using STATICS but it is not "pretty" and consumes more memory.

      I still can’t see the rationale of putting references to local data objects into a global internal table.

      Let my try to explain what actually I am doing. All variables are local:

        DATA lst_scarr    TYPE SORTED TABLE OF scarr
                                      WITH UNIQUE KEY carrid.
        DATA ls_scarr     TYPE scarr.
      
        SELECT * FROM sflight INTO TABLE @DATA(lt_flight).
      
        LOOP AT lt_flight INTO DATA(ls_flight).
          zcldb=>fetch(
            EXPORTING
              iv_table  = 'SCARR'
              it_key    = lt_flight
              is_key    = ls_flight
              iv_comp   = 'CARRID'
            IMPORTING
              e_data    = ls_scarr
            CHANGING
              ct_all    = lst_scarr
          ).
          
          " The code above is totaly the same as
          AT FIRST.
            SELECT *
              INTO TABLE @lst_scarr
              FROM scarr
              FOR ALL ENTRIES IN @lt_flight
              WHERE carrid = @lt_flight-carrid.
          ENDAT.
          
          READ TABLE lst_scarr INTO ls_scarr
                               WITH TABLE KEY carrid = ls_flight-carrid.
        ENDLOOP.

      I need to know if the selection was done using the table lt_flight or not. There is a static table in a class which collects references of it_key. And when it_key is popped from the stack this static table becomes invalid. I don't know was it_key local or not.

      The only way to workaround this problem I see that you suggested me:

      putting references to local data objects of a class into an internal table that is an attribute of the class

      But it is not comfortable to create local classes in every method/procedure. I wanted to use static methods...

      Author's profile photo Horst Keller
      Horst Keller
      Blog Post Author

       

      I see your point now.

       

      The question is, why do the internal table statements try to access the unbound/invalid reference variables instead of treating them like bound ones.

       

      Other examples:

       

      • If have a sorted table of reference variables, I cannot insert lines if there are already invalid ones.

         

      • If have a standard table of reference variables, I cannot SORT if there are invalid ones … .

      Interesting enough, I can read an invalid or insert  an invalid if no sorting or key access is involved.

       

      I will ask the internal table guys about that restriction.

      Author's profile photo Former Member
      Former Member

      Yes, Horst! It is the case! Thank you a lot! 🙂

      Author's profile photo Horst Keller
      Horst Keller
      Blog Post Author

       

      Iurii,

      There was in fact a gap in the documentation. Therefore, I answer in a longer blog.

      Unfortunately, the answer won't make you happy.

      Horst

      Author's profile photo Former Member
      Former Member

      It is what it is. Thank you for investigation 🙂