байюнь
Все видео: https://segmentfault. com/a/11…
Условие запуска сборки мусора
- Мы знаем, что в PHP, если количество ссылок на переменную уменьшено до 0 (эта переменная нигде не используется), занимаемая ею память будет автоматически переработана виртуальной машиной PHP. Это не будет мусором. 。 Условие запуска сборки мусора заключается в том, что когда количество ссылок переменной уменьшается на 1, оно все равно не равно 0 (и эта переменная используется в каком-то месте). Будь на карточках Это мусор. Нам нужно вручную протестировать его, чтобы убедиться, что это действительно мусор, а затем выполнить последующие операции. Типичный пример-в нашем использовании массив И объект Может существовать в процессе Круговой ссылки Проблемы. Он сам создает ссылку на переменную. Вот пример:
- Мы видим, что после снятия ($a) тип $a становится 0 (is ﹣ undef), а ссылка на структуру ссылок Zend, на которую он указывает, становится 1 (потому что элемент в массиве $a все еще ссылается на него). Давайте нарисуем картинку, чтобы показать текущую ситуацию с памятью:
- Таким образом, проблема в том, что $a не установлен, но поскольку элементы в исходном zend_array все еще указывают на структуру zend_reference, ссылка на zend_reference равна 1, а не ожидаемому 0. Таким образом, эти две структуры Zend ﹣ ссылки и Zend ﹣ массива все еще существуют в памяти после снятия ($a). Если с этим ничего не сделать, это приведет к Утечке памяти 。
- Пожалуйста, обратитесь к [Изучение исходного кода PHP] 2019-03-19 Ссылка на PHP для приведенного выше подробного объяснения.
- Итак, как устранить утечку памяти, вызванную циклической ссылкой? Наш Вывоз мусора Это может пригодиться.
- В php7 сбор мусора разделен на Сборщик мусора и Алгоритм сбора мусора Две части
- Только первая часть этой заметки: сборщик мусора
Сборщик мусора
- В php 7, если обнаружена переменная, количество ссылок которой все еще больше 0 после вычитания 1, она будет помещена в Двусторонний связанный список Это наш сборщик мусора. Этот сборщик мусора эквивалентен буферу После заполнения буфера дождитесь, пока алгоритм сбора мусора отметит и очистит его.
- Время запуска алгоритма сбора мусора-это не просто предполагаемый сбор мусора, он должен запускаться один раз, но после того, как буфер заполнится (указано 10001 единица хранения), алгоритм сбора мусора запустится, и подозрительный мусор в буфере будет окончательно помечен и очищен. Функция буфера сборщика мусора заключается в снижении частоты работы алгоритма сбора мусора, уменьшении занятости ресурсов операционной системы и влияния на выполняемый серверный код. Давайте подробно объясним это с помощью кода.
Структура хранения сборщика мусора
- Структура сборщика мусора выглядит следующим образом:
typedef struct _gc_root_buffer {
zend_refcounted *ref;
Struct GC root buffer * next; // double linked list, pointing to the next buffer unit
Struct GC root buffer * prev; // a two-way linked list pointing to the previous buffer unit
uint32_t refcount;
} gc_root_buffer;- Сборщик мусора представляет собой двусторонний связанный список, так как же сохранить информацию о первом и последнем указателях двустороннего связанного списка, а также об использовании буферов и другой дополнительной информации? Теперь нам нужно использовать нашу глобальную переменную zend_gc_global:
typedef struct _zend_gc_globals {
Zend bool GC enable; // enable GC or not
Zend bool GC active; // whether GC is currently running
Zend? Bool GC? Full; // whether the buffer is full
GC root buffer * buf; / * points to the buffer header*/
GC? Root? Buffer roots; / * the garbage buffer unit currently processed. Note that this is not a pointer.*/
GC ﹣ root ﹣ buffer * Unused; / * points to the beginning of the unused buffer unit list (used to concatenate buffer fragments)*/
GC? Root? Buffer * first? Unused; / * points to the first unused buffer unit*/
GC? Root? Buffer * last? Unused; / * points to the last unused buffer unit*/
gc_root_buffer to_free;
gc_root_buffer *next_to_free;
...
} zend_gc_globals;Инициализация сборщика мусора
- Теперь нам нужно выделить место в памяти сборщику мусора для хранения подозрительного мусора, который может появиться в следующий раз. Мы используем функцию GC init() для выделения пространства:
ZEND_API void gc_init(void)
{
if (GC_G(buf) == NULL && GC_G(gc_enabled)) {
GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);
GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
gc_reset();
}
}- Макрокоманда GC предназначена для получения переменных в глобальной структуре Zend GC. Мы еще не создали буфер, поэтому переходим к этой ветви if. Выделите блок памяти, вызвав malloc. Размер этой памяти равен размеру одной буферной структуры * 10001:
#define GC_ROOT_BUFFER_MAX_ENTRIES 10001
- Теперь мы получаем буфер размером 10001 (первый блок не используется) и устанавливаем размер шага указателя на тип gc_root_buffer, затем указываем его указатель last_unused в конец буфера, а затем выполняем некоторые операции инициализации через gc_reset():
ZEND_API void gc_reset(void)
{
GC_G(gc_runs) = 0;
GC_G(collected) = 0;
GC_G(gc_full) = 0;
...
GC_G(roots).next = &GC_G(roots);
GC_G(roots).prev = &GC_G(roots);
GC_G(to_free).next = &GC_G(to_free);
GC_G(to_free).prev = &GC_G(to_free);
If (gc_g (buf)) {// because we allocated buffer before, enter here
Gc_g (Unused) = null; // no buffer fragment, set the pointer to null
GC ﹣ g (first ﹣ unused) = GC ﹣ g (buf) + 1; // move the pointer to the first unused space one unit back
} else {
GC_G(unused) = NULL;
GC_G(first_unused) = NULL;
GC_G(last_unused) = NULL;
}
GC_G(additional_buffer) = NULL;
}- В соответствии с содержанием этой функции мы можем нарисовать диаграмму текущей структуры памяти:
Храните подозрительные отходы в мусоросборнике
- Таким образом, наш буфер сборщика мусора инициализируется. Теперь подождите, пока виртуальная машина Zend соберет переменные, которые могут быть мусором, и сохранит их в этих буферах. Этот шаг выполняется функцией GC ﹣ possible ﹣ root (Zend ﹣ refcounted * ref).:
ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
{
gc_root_buffer *newRoot;
if (UNEXPECTED(CG(unclean_shutdown)) || UNEXPECTED(GC_G(gc_active))) {
return;
}
ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
ZEND_ASSERT(EXPECTED(GC_REF_GET_COLOR(ref) == GC_BLACK));
ZEND_ASSERT(!GC_ADDRESS(GC_INFO(ref)));
GC_BENCH_INC(zval_possible_root);
newRoot = GC_G(unused);
if (newRoot) {
GC_G(unused) = newRoot->prev;
} else if (GC_G(first_unused) != GC_G(last_unused)) {
newRoot = GC_G(first_unused);
GC_G(first_unused)++;
} else {
if (!GC_G(gc_enabled)) {
return;
}
GC_REFCOUNT(ref)++;
gc_collect_cycles();
GC_REFCOUNT(ref)--;
if (UNEXPECTED(GC_REFCOUNT(ref)) == 0) {
zval_dtor_func(ref);
return;
}
if (UNEXPECTED(GC_INFO(ref))) {
return;
}
newRoot = GC_G(unused);
if (!newRoot) {
#if ZEND_GC_DEBUG
if (!GC_G(gc_full)) {
fprintf(stderr, "GC: no space to record new root candidate\n");
GC_G(gc_full) = 1;
}
#endif
return;
}
GC_G(unused) = newRoot->prev;
}
GC_TRACE_SET_COLOR(ref, GC_PURPLE);
GC_INFO(ref) = (newRoot - GC_G(buf)) | GC_PURPLE;
newRoot->ref = ref;
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots);
GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot;
GC_BENCH_INC(zval_buffered);
GC_BENCH_INC(root_buf_length);
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
}- Не имеет значения, если код немного длинноват. Давайте проанализируем строку за строкой. Сначала объявляется указатель на буфер, новый корень. Затем рассудите, что если сборщик мусора уже запущен, на этот раз он не будет выполнен. Затем назначьте неиспользуемое поле указателя в глобальной переменной Zend ﹣ GC ﹣ globals указателю newroot. Однако неиспользуемый указатель равен нулю (поскольку фрагмента буфера нет), поэтому новый корень также равен нулю в это время. Итак, далее введите ветвь else if:
newRoot = GC_G(first_unused);
GC_G(first_unused)++;- Во-первых, укажите newroot на первый неиспользуемый буферный блок, поэтому в следующей строке необходимо переместить первый неиспользуемый буферный блок обратно на один блок, чтобы облегчить следующее использование. Это легко понять. Пропустите эту длинную ветвь else и продолжайте выполнение:
GC_TRACE_SET_COLOR(ref, GC_PURPLE);
GC_INFO(ref) = (newRoot - GC_G(buf)) | GC_PURPLE;
newRoot->ref = ref;
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots);
GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot;- Первая строка, трассировка GC, используется для печати соответствующей отладочной информации. Мы пропускаем эту строку.
- Во второй строке выполняется gc_info (Ref) = (новый корень – gc_g (buf)) | gc_purple; мы видим, что существует понятие gc_purple, то есть цвета. В сборке мусора PHP используются четыре цвета:
#define GC_BLACK 0x0000 #define GC_WHITE 0x8000 #define GC_GREY 0x4000 #define GC_PURPLE 0xc000
- Они объясняются в исходном коде следующим образом:
* BLACK (GC_BLACK) - In use or free. * GREY (GC_GREY) - Possible member of cycle. * WHITE (GC_WHITE) - Member of garbage cycle. * PURPLE (GC_PURPLE) - Possible root of cycle.
- Мы не будем подробно объяснять здесь каждый цвет. Мы используем (newroot – gc_g (buf)) | gc_purple означает: newroot – gc_g (buf) (начальный адрес буфера) представляет смещение используемого в данный момент буфера, а затем выполняет или работает с 0xc000. Результат собирается в поле gc_info переменной, которая имеет тип uint16, поэтому первые два бита могут быть помечены фиолетовым цветом, а последние 14 бит используются для хранения смещения. Последнее поле делится на бит, как показано на рисунке:
- Строка 3: назначьте текущую ссылку текущему буферу
- Далее следует операция с указателем в двустороннем связанном списке:
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots);
GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot;- Его цель состоит в том, чтобы указать указатели prev и next текущего буфера на корневое поле в глобальной переменной и указать указатели prev и next корневого поля в глобальной переменной на используемый в данный момент буфер.
- На этом этапе мы можем поместить все переменные, подозреваемые в мусоре, в буфер и сохранить их. Когда буфер заполнится 10000 единицами хранения, запустится алгоритм сбора мусора, пометит и очистит весь подозрительный мусор в буфере. Процесс алгоритма сбора мусора будет объяснен в следующей заметке.