Понятие слабой ссылки существует в системах и языках программирования, где поддерживается сборка мусора — автоматическое удаление из памяти объектов, использование которых прекратилось и более не будет возобновлено. Далее в статье будет рассмотрена реализация слабых ссылок в ABAP.
Для определения объектов, подлежащих сборке мусора, используется тот или иной вариант алгоритма определения достижимости — достижимым считается объект, на который в программе существует хотя бы одна ссылка. Когда в программе не осталось ни одной ссылки на объект, то есть использование объекта прекратилось, такой объект может быть удалён в ближайший подходящий момент.
Сборщик мусора в ABAP устроен таким образом, что освобождает объект из памяти тогда, когда на него нет ссылки, и если методы объекта не подписаны на события иного объекта, ссылка на который еще жива. Сам по себе он вызывается «периодически» средой исполнения, но может быть вызван и вручную через вызов метода:
1 |
cl_abap_memory_utilities=>do_garbage_collection( ). |
Описанный механизм освобождения памяти может в некоторых случаях порождать утечки памяти из-за «забытых» ссылок, когда ссылки на создаваемые объекты сохраняются в нескольких местах, и при прекращении использования объекта программист не удаляет их все. Во избежание проблем программист вынужден придерживаться достаточно жёсткой дисциплины в использовании ссылок, что не всегда удобно.
Чтобы избежать подобных проблем, язык или среда программирования могут поддерживать так называемые слабые ссылки. Такие ссылки используются так же, как и обычные, но не влияют на сборку мусора, поскольку не учитываются механизмом подсчёта ссылок.
В ABAP слабую ссылку можно получить через класс CL_ABAP_WEAK_REFERENCE, делается это следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
CLASS lcl_test DEFINITION. ENDCLASS. CLASS lcl_test IMPLEMENTATION. ENDCLASS. DATA: lo_weak_reference TYPE REF TO cl_abap_weak_reference, lo_strong_reference TYPE REF TO lcl_test. " Создаем сильную ссылку CREATE OBJECT lo_strong_reference. " Создаем на её основе слабую ссылку CREATE OBJECT lo_weak_reference EXPORTING oref = lo_strong_reference. " Освобождаем сильную ссылку CLEAR lo_strong_reference. " Если убрать комментарий, будет вызван сборщик мусора, " который найдет объект на который нет сильной " ссылки и удалит его из памяти, восстановить по слабой ссылке не получится. " cl_abap_memory_utilities=>do_garbage_collection( ). " Получаем сильную ссылку на основе слабой lo_strong_reference ?= lo_weak_reference->get( ). IF lo_strong_reference IS NOT BOUND. " Сборщик мусора уже мог успеть очистить память, " по слабой ссылке восстановить сильную не удалось, " необходимо инициализировать объект заново. CREATE OBJECT lo_strong_reference. WRITE: 'Восстановить не удалось, создаем объект заново.'. ELSE. WRITE: 'Объект был получен по слабой ссылке.'. ENDIF. |
Слабые ссылки часто используются для обеспечения кэша, такой кэш не требует тщательного отслеживания допустимой памяти, если на этапе работы программы памяти будет не достаточно, кэш будет очищен сборщиком мусора автоматически, причем очистка происходит по частям:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
CLASS lcl_weak_cache DEFINITION. PUBLIC SECTION. CLASS-METHODS: set IMPORTING iv_id TYPE csequence io_reference TYPE REF TO object, get IMPORTING iv_id TYPE csequence RETURNING VALUE(ro_reference) TYPE REF TO object. PRIVATE SECTION. TYPES: BEGIN OF ty_cache_line, id TYPE string, object TYPE REF TO cl_abap_weak_reference, END OF ty_cache_line . TYPES: ty_cache_table TYPE HASHED TABLE OF ty_cache_line WITH UNIQUE KEY id . CLASS-DATA gt_cache TYPE ty_cache_table . ENDCLASS. "lcl_weak_cache DEFINITION CLASS lcl_weak_cache IMPLEMENTATION. METHOD set. DATA: ls_cache TYPE ty_cache_line. DELETE gt_cache WHERE id = iv_id. ls_cache-id = iv_id. CREATE OBJECT ls_cache-object EXPORTING oref = io_reference. INSERT ls_cache INTO TABLE gt_cache. ENDMETHOD. "set METHOD get. FIELD-SYMBOLS: <ls_cache> TYPE ty_cache_line. READ TABLE gt_cache WITH TABLE KEY id = iv_id ASSIGNING <ls_cache>. CHECK sy-subrc EQ 0. ro_reference = <ls_cache>-object->get( ). ENDMETHOD. "get ENDCLASS. "lcl_weak_cache IMPLEMENTATION CLASS lcl_cached_object DEFINITION. PUBLIC SECTION. METHODS: constructor. ENDCLASS. "lcl_cached_object DEFINITION CLASS lcl_cached_object IMPLEMENTATION. METHOD constructor. " Инициализация объекта ENDMETHOD. "constructor ENDCLASS. "lcl_cached_object IMPLEMENTATION DATA: go_cached_object TYPE REF TO lcl_cached_object, gv_index TYPE string. START-OF-SELECTION. DO 1000000 TIMES. gv_index = sy-index. CONDENSE gv_index NO-GAPS. CREATE OBJECT go_cached_object. lcl_weak_cache=>set( iv_id = gv_index io_reference = go_cached_object ). CLEAR go_cached_object. ENDDO. go_cached_object ?= lcl_weak_cache=>get( `2` ). IF go_cached_object IS NOT BOUND. WRITE 'Объект под номером 2 не восстановлен.'. go_cached_object ?= lcl_weak_cache=>get( `999999` ). CHECK go_cached_object IS BOUND. WRITE 'Объект под номером 999999 восстановлен по слабой ссылке'. ENDIF. |
Если перед получением объекта 999999 вызвать сборщик мусора вручную, весь кэш будет очищен (в автоматическом режиме отчищается по частям) и объект не будет восстановлен по слабой ссылке.
В данном примере после заполнения кэша большинство слабых ссылок уже не даст указатель на сильную ссылку, происходит это из-за того что при попытке создать слабую ссылку будет запущен сборщик мусора и память относительно ранее выделенных объектов будет освобождена.
Чтобы весь кэш был доступен после заполнения можно расширить структуру кэша на сильную ссылку и чистить её отдельным методом после заполнения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
CLASS lcl_weak_cache DEFINITION. PUBLIC SECTION. CLASS-METHODS: set IMPORTING iv_id TYPE csequence io_reference TYPE REF TO object, get IMPORTING iv_id TYPE csequence RETURNING value(ro_reference) TYPE REF TO object, clear. PRIVATE SECTION. TYPES: BEGIN OF ty_cache_line, id TYPE string, object TYPE REF TO object, weak TYPE REF TO cl_abap_weak_reference, END OF ty_cache_line . TYPES: ty_cache_table TYPE HASHED TABLE OF ty_cache_line WITH UNIQUE KEY id . CLASS-DATA gt_cache TYPE ty_cache_table . ENDCLASS. "lcl_weak_cache DEFINITION CLASS lcl_weak_cache IMPLEMENTATION. METHOD set. DATA: ls_cache TYPE ty_cache_line. DELETE gt_cache WHERE id = iv_id. ls_cache-id = iv_id. ls_cache-object = io_reference. CREATE OBJECT ls_cache-weak EXPORTING oref = ls_cache-object. INSERT ls_cache INTO TABLE gt_cache. ENDMETHOD. "set METHOD get. FIELD-SYMBOLS: <ls_cache> TYPE ty_cache_line. READ TABLE gt_cache WITH TABLE KEY id = iv_id ASSIGNING <ls_cache>. CHECK sy-subrc EQ 0. ro_reference = <ls_cache>-weak->get( ). ENDMETHOD. "get METHOD clear. FIELD-SYMBOLS: <ls_cache> TYPE ty_cache_line. LOOP AT gt_cache ASSIGNING <ls_cache>. CLEAR <ls_cache>-object. ENDLOOP. ENDMETHOD. "to_weak ENDCLASS. "lcl_weak_cache IMPLEMENTATION CLASS lcl_cached_object DEFINITION. PUBLIC SECTION. METHODS: constructor. ENDCLASS. "lcl_cached_object DEFINITION CLASS lcl_cached_object IMPLEMENTATION. METHOD constructor. " Инициализация объекта ENDMETHOD. "constructor ENDCLASS. "lcl_cached_object IMPLEMENTATION DATA: go_cached_object TYPE REF TO lcl_cached_object, gv_index TYPE string. START-OF-SELECTION. DO 99999 TIMES. gv_index = sy-index. CONDENSE gv_index NO-GAPS. CREATE OBJECT go_cached_object. lcl_weak_cache=>set( iv_id = gv_index io_reference = go_cached_object ). CLEAR go_cached_object. ENDDO. lcl_weak_cache=>clear( ). go_cached_object ?= lcl_weak_cache=>get( `1` ). |
Как ранее уже упоминалось, объект будет удален тогда сборщиком мусора, когда на него нет сильной ссылки и методы этого объекта не указаны в качестве обработчика события объекта, который еще жив. Далее рассмотрим пример, где это проявляется:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
CLASS lcl_class_a DEFINITION. PUBLIC SECTION. EVENTS: some_event EXPORTING value(some_data) TYPE string. ENDCLASS. "lcl_class_a DEFINITION CLASS lcl_class_a IMPLEMENTATION. ENDCLASS. "lcl_class_a IMPLEMENTATION CLASS lcl_class_b DEFINITION. PUBLIC SECTION. METHODS: handler FOR EVENT some_event OF lcl_class_a. ENDCLASS. "lcl_class_b DEFINITION CLASS lcl_class_b IMPLEMENTATION. METHOD handler. ENDMETHOD. "handler ENDCLASS. "lcl_class_b IMPLEMENTATION START-OF-SELECTION. DATA: lo_a TYPE REF TO lcl_class_a, lo_b TYPE REF TO lcl_class_b. CREATE OBJECT lo_a. CREATE OBJECT lo_b. SET HANDLER lo_b->handler FOR lo_a. CLEAR lo_b. cl_abap_memory_utilities=>do_garbage_collection( ). BREAK-POINT. |
Перейдя в отладчике в инструмент анализа памяти мы увидим следующую картину:
Как видно из картинки, объект памяти, ссылающийся на экземпляр класса b жив, несмотря на то, что был вызван сборщик мусора.
Добавив деактивацию подписки на событие, мы убедимся, что описанное ранее правило действительно работает и объект будет удален, если в нем нет подписки на события объекта класса «А»: