«Hardcore JS» может иметь утечку памяти в вашей программе

внешний интерфейс JavaScript Chrome
«Hardcore JS» может иметь утечку памяти в вашей программе

Отказ от ответственности: эта статья является первой подписанной статьей Nuggets, и ее перепечатка без разрешения запрещена.

написать впереди

Я думаю, многие студенты видят утечки памяти, и в их сердцах всплывают два слова: закрытие! ! ! Скажи еще что-нибудь и промолчи. Если ваше понимание утечек памяти ограничено замыканиями, вам действительно следует внимательно прочитать эту статью, Замыкания могут вызывать утечки памяти, но утечки памяти — это не только замыкания, это всего лишь одно из представлений об утечках памяти.

Программа, которую вы пишете медленно, зависает или даже дает сбой после работы в течение определенного периода времени?

Например, в вашей программе могут быть утечки памяти, когда дело доходит до утечек памяти, рекомендуется сначала прочитать"Hardcore JS" Вы действительно понимаете механизм сборки мусора?Одну статью, а потом читать эту статью будет прозрачнее, ведь сборка мусора и утечки памяти - это причинно-следственные связи, в сборе мусора нет ничего плохого, а утечки памяти, если мусор не собран.

В этой статье мы познакомим вас с соответствующими концепциями утечек памяти и некоторыми проблемами, вызывающими утечки памяти, а также сосредоточимся на знакомстве с методами устранения неполадок, обнаружения и устранения утечек памяти (вы сможете использовать их, как только изучите их). Наконец, мы просто расширим три утечки памяти во внешнем интерфейсе.Две другие крупные утечки — это концепция раздувания памяти и частые сборщики мусора.

что такое утечка памяти

В движке есть механизм сборки мусора, который в основном нацелен на объекты, которые больше не используются в некоторых программах, и очищает и перерабатывает их для освобождения памяти.

Так будет ли механизм сборки мусора собирать все объекты (мусор), которые больше не используются?

На самом деле, хотя движок сделал различные оптимизации для сборки мусора, чтобы обеспечить максимально возможную переработку мусора, это не значит, что мы можем вообще не заботиться об этом.В нашем коде нам все равно нужно активно избегать некоторых операции, которые не способствуют сборке мусора движком.Поскольку не вся бесполезная объектная память может быть переработана, когда объектная память, которая больше не используется, не утилизируется вовремя, мы называем это内存泄漏(Memory leak).

Распространенные утечки памяти

Код не стандартизирован, а у коллеги две строчки слез.Далее давайте рассмотрим некоторые распространенные случаи, вызывающие утечку памяти.

Неправильное закрытие

Замыкания — это функции, вложенные внутрь и возвращающие функцию? ? ? Это то, что большинство людей думают о замыканиях, что ж, давайте взглянем на описания в нескольких книгах по JS:

  • Расширенное программирование на JavaScript: замыкания — это функции, которые имеют доступ к переменным в области действия другой функции.
  • Полное руководство по JavaScript: с технической точки зрения все функции JavaScript являются замыканиями: они являются объектами и связаны с цепочкой областей видимости.
  • JavaScript, о котором вы не знали: замыкание происходит, когда функция может запомнить и получить доступ к лексической области видимости, в которой она находится, даже если функция выполняется за пределами текущей лексической области видимости.

Согласно описаниям в трех вышеприведенных книгах, область применения замыкания относительно широка.Мы не будем пока запутываться с определением замыкания, а возьмем самый простой и наиболее узнаваемый пример замыкания, чтобы посмотреть на замыкание:

function fn1(){
  let test = new Array(1000).fill('isboyjc')
  return function(){
    console.log('hahaha')
  }
}
let fn1Child = fn1()
fn1Child()

Является ли приведенный выше пример закрытием? Это вызвало утечку памяти?

Очевидно, что это типичное закрытие, но оно не вызывает утечки памяти, поскольку возвращаемая функция неfn1Ссылка внутри функции, то есть функцияfn1ВнутреннийtestПеременные полностью перерабатываемы, так что давайте посмотрим на:

function fn2(){
  let test = new Array(1000).fill('isboyjc')
  return function(){
    console.log(test)
    return test
  }
}
let fn2Child = fn2()
fn2Child()

Является ли приведенный выше пример закрытием? Это вызвало утечку памяти?

Видимо это тоже замыкание, а потомуreturnВ функции есть функцияfn2серединаtestссылка на переменную, поэтомуtestОн не будет переработан, что вызовет утечку памяти.

Итак, как решить эту проблему?

На самом деле, после вызова функции полезно очистить отношение внешней ссылки следующим образом:

function fn2(){
  let test = new Array(1000).fill('isboyjc')
  return function(){
    console.log(test)
    return test
  }
}
let fn2Child = fn2()
fn2Child()
fn2Child = null

«Уменьшите использование замкнутых, которые могут вызвать утечки памяти ...»

Просыпайтесь, это предложение в прошедшем времени, его описание неточное. Итак, следует сказать, что неправильное использование замыканий может вызвать утечку памяти.

неявная глобальная переменная

мы знаемJavaScriptСборка мусора выполняется автоматически, и сборщик мусора будет через определенные промежутки времени обнаруживать данные, которые больше не используются, и освобождать занимаемое ими место в памяти.

Давайте посмотрим на глобальные переменные и локальные переменные, локальные переменные в функции больше не нужны после окончания выполнения функции, поэтому сборщик мусора распознает и освободит их. Но для глобальных переменных сборщику мусора трудно определить, когда эти переменные не нужны, поэтому глобальные переменные обычно не перерабатываются.Мы можем использовать глобальные переменные, но в то же время нам нужно избегать некоторых дополнительных глобальные переменные, как показано ниже:

function fn(){
  // 没有声明从而制造了隐式全局变量test1
  test1 = new Array(1000).fill('isboyjc1')
  
  // 函数内部this指向window,制造了隐式全局变量test2
  this.test2 = new Array(1000).fill('isboyjc2')
}
fn()

функция вызоваfn, потому что объявления нет и проблема этого в функции вызывает две дополнительные неявные глобальные переменные, которые не будут переработаны.Нам следует максимально избегать этой ситуации.В разработке мы можем использовать строгий режим или передатьlintОтметьте, чтобы избежать таких ситуаций, тем самым уменьшая затраты памяти.

Кроме того, мы неизбежно будем использовать глобальные переменные в программе.Эти глобальные переменные не могут быть переработаны, если только они не будут отменены или переназначены, что требует нашего дополнительного внимания, то есть когда мы используем Когда глобальная переменная хранит данные, обязательно очистите или переназначить его после использования.Конечно, это тоже очень просто.После использования установите его наnullТо есть, особенно при использовании глобальных переменных в качестве кеша, постоянно хранящего большое количество данных, мы должны помнить о том, чтобы установить лимит хранения и вовремя очистить его, иначе объем данных увеличится, а также увеличится нагрузка на память.

var test = new Array(10000)

// do something

test = null

Бесплатные ссылки на DOM

Учитывая производительность или простоту кода, мы будем использовать переменные для кэширования ссылок на узлы DOM при выполнении DOM в нашем коде, но при удалении узлов мы должны освобождать кэшированные ссылки синхронно, иначе свободные поддеревья не могут быть освобождены.

<div id="root">
  <ul id="ul">
    <li></li>
    <li></li>
    <li id="li3"></li>
    <li></li>
  </ul>
</div>
<script>
  let root = document.querySelector('#root')
  let ul = document.querySelector('#ul')
  let li3 = document.querySelector('#li3')
  
  // 由于ul变量存在,整个ul及其子元素都不能GC
  root.removeChild(ul)
  
  // 虽置空了ul变量,但由于li3变量引用ul的子节点,所以ul元素依然不能被GC
  ul = null
  
  // 已无变量引用,此时可以GC
  li3 = null
</script>

Как показано выше, когда мы используем переменную для кэширования ссылки на узел DOM и удаляем узел, если переменная, на которую ссылается кеш, не пуста, сборщик мусора по-прежнему не может быть выполнен, и произойдет утечка памяти.

Если мы оставим родительский узел пустым, но ссылки на дочерние узлы удаленного родительского узла также кэшируются в переменных, тогда все свободное дерево узлов под всем родительским деревом узлов DOM не будет очищено или возникнут утечки памяти, решение То есть переменные, которые ссылаются на дочерние узлы, также пусты, как показано ниже:

забытый таймер

Мы часто используем таймеры в программах, т.е.setTimeoutиsetInterval, сначала посмотрите на пример:

// 获取数据
let someResource = getData()
setInterval(() => {
  const node = document.getElementById('Node')
	if(node) {
    node.innerHTML = JSON.stringify(someResource))
	}
}, 1000)

выше это яcopyНебольшой пример кода, который помещает полученные данные вNodeузел, но вsetIntervalДо конца переменные в функции обратного вызова и сама функция обратного вызова не могут быть переработаны.

Что значит закончить? то есть звонитьclearInterval. если неclearЕсли его сбросить, это вызовет утечку памяти. Мало того, если функция обратного вызова не переработана, то и зависимые переменные в функции обратного вызова не могут быть переработаны. Итак, в приведенном выше примереsomeResourceне может быть переработан.

такой же,setTiemoutбудет та же проблема, поэтому, когда не нужноintervalилиtimeoutкогда лучше звонитьclearIntervalилиclearTimeoutочистить, кроме того,requestAnimationFrameСуществует также эта проблема, мы должны использовать ее, когда она нам не нужна.cancelAnimationFrameAPI для отмены использования.

Слушатель забытых событий

Когда прослушиватель событий монтирует соответствующий обработчик событий в компоненте, но не очищает его активно при уничтожении компонента, указанные переменные или функции считаются необходимыми и не будут повторно использоваться. данных может привести к тому, что страницы будут занимать слишком много памяти, что может привести к неожиданным утечкам памяти.

Возьмем в качестве примера компоненты Vue, то же самое верно и в React:

<template>
  <div></div>
</template>

<script>
export default {
  created() {
    window.addEventListener("resize", this.doSomething)
  },
  beforeDestroy(){
    window.removeEventListener("resize", this.doSomething)
  },
  methods: {
    doSomething() {
      // do something
    }
  }
}
</script>

Забытый режим слушателя

Мы все знаем режим слушателя, будь то Vue, React или другие, для текущей среды разработки интерфейса, очень часто режим слушателя реализует некоторую передачу сообщений, напримерEventBus. . .

Когда мы реализуем режим слушателя и монтируем соответствующие обработчики событий в компоненте, но не очищаем его активно при уничтожении компонента, ссылочные переменные или функции считаются необходимыми и не будут переработаны. много данных, что может привести к тому, что страницы будут занимать слишком много памяти, что также может привести к неожиданным утечкам памяти.

Давайте использовать компоненты Vue в качестве примера, потому что это относительно просто:

<template>
  <div></div>
</template>

<script>
export default {
  created() {
    eventBus.on("test", this.doSomething)
  },
  beforeDestroy(){
    eventBus.off("test", this.doSomething)
  },
  methods: {
    doSomething() {
      // do something
    }
  }
}
</script>

Как и выше, нам нужно толькоbeforeDestroyЕго можно очистить во время жизненного цикла уничтожения компонента.

Забытые карты и наборы объектов

когда используешьMapилиSetПри хранении объектов то же самоеObjectНепротиворечивость — это сильная ссылка.Если ссылка не очищается активно, это также приведет к тому, что память не будет автоматически переработана.

При использованииMap, для случая, когда ключ является объектом, вы можете использоватьWeakMap,WeakMapОбъекты также используются для хранения пар ключ-значение, которые являются слабыми ссылками на ключи (Примечание:WeakMapТолько для ключа это слабая ссылка) и должен быть объект, а значением может быть любой объект или примитивное значение, т.к. это слабая ссылка на объект, мешать не будетJsвывоз мусора.

При необходимости используйтеSetссылочный объект, вы можете использоватьWeakSet,WeakSetОбъекты позволяют хранить уникальные значения, на которые слабо ссылаются объекты,WeakSetЗначение в объекте не будет повторяться, и может быть сохранена только слабая ссылка на объект.Также, поскольку это слабая ссылка на объект, она не будет мешатьJsвывоз мусора.

Здесь нам может понадобиться краткое введение. Говоря о слабых ссылках, давайте сначала поговорим о сильных ссылках. Прежде чем мы сказали, что механизм сборки мусора JS заключается в том, что если мы храним ссылку на объект, то этот объект не будет собирать мусор. Ссылка здесь, что означает强引用, а слабая ссылка — это объект, который считается недоступным (или слабодоступным), если на него ссылаются только слабые ссылки, поэтому он может быть переработан в любое время.

не понимать? Просто посмотрите на пример:

// obj是一个强引用,对象存于内存,可用
let obj = {id: 1}

// 重写obj引用
obj = null 
// 对象从内存移除,回收 {id: 1} 对象

Выше приведен простой способ очистить ссылку на объект, переопределив ссылку, сделав ее пригодной для повторного использования.

Посмотрите еще раз:

let obj = {id: 1}
let user = {info: obj}
let set = new Set([obj])
let map = new Map([[obj, 'hahaha']])

// 重写obj
obj = null 

console.log(user.info) // {id: 1}
console.log(set)
console.log(map)

В этом примере мы перепишемobjпосле,{id: 1}останется в памяти, потому чтоuserобъект и следующееset/mapНа него строго ссылаются, Set/Map, объекты, объекты массива и т. д. являются сильными ссылками, поэтому мы все еще можем получить{id: 1}, если мы хотим его очистить, мы можем только переписать все ссылки, чтобы сделать его пустым.

Далее мы видимWeakMapа такжеWeakSet:

let obj = {id: 1}
let weakSet = new WeakSet([obj])
let weakMap = new WeakMap([[obj, 'hahaha']])

// 重写obj引用
obj = null

// {id: 1} 将在下一次 GC 中从内存中删除

Как показано выше, с помощьюWeakMapа такжеWeakSetявляется слабой ссылкой,objссылка установлена ​​наnullПосле объекта{id: 1}Память будет очищена при следующем сборщике мусора.

Недезинфицированный вывод консоли

В процессе написания кода не избежать некоторого вывода, в некоторых небольших командах проект может уйти в онлайн и не очиститься.console, не зная этихconsoleЭто также скрытая опасность, и ее также легко игнорировать Причина, по которой мы можем видеть вывод данных в консоли, заключается в том, что браузер сохраняет информационную ссылку на данные нашего объекта вывода, поэтому он не очищается .consoleЕсли объект экспортируется, это также вызовет утечку памяти.

Поэтому в среде разработки мы можем использовать вывод консоли для облегчения нашей отладки, но в производственной среде мы должны вовремя очищать вывод.

Некоторым студентам это может показаться невероятным или даже невероятным. Здесь мы оставляем пример. Прочитав статью, вы можете просто проверить это самостоятельно. Вы можете сначала сохранить этот код! (Как его протестировать будет понятно после прочтения следующего)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>

<body>
  <button id="click">click</button>

  <script>
    !function () {
      function Test() {
        this.init()
      }
      Test.prototype.init = function () {
        this.a = new Array(10000).fill('isboyjc')
        console.log(this)
      }

      document.querySelector('#click').onclick = function () {
        new Test();
      }
    }()
  </script>
</body>

</html>

Расследование утечки памяти, локализация и устранение

Как упоминалось в начале, программа будет медленно зависать или даже давать сбой после запуска в течение определенного периода времени.Я не знаю причины, поэтому давайте рассмотрим весь процесс проверки, обнаружения и устранения утечек памяти на примере, стучать по доске, это каждый может действительно использовать.

Поскольку выше мы упомянули несколько случаев, вызывающих утечку памяти, давайте воспользуемся этими случаями для написанияDemoС точки зрения браузера проверьте, есть ли утечка памяти, если есть, найдите источник утечки и устраните его.

Во-первых, давайте составим пример утечки памяти:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>test</title>
</head>

<body>
  <button id="click">click</button>
  <h1 id="content"></h1>

  <script>
    let click = document.querySelector("#click");
    let content = document.querySelector("#content")
    let arr = []

    function closures() {
      let test = new Array(1000).fill('isboyjc')

      return function () {
        return test
      }
    }

    click.addEventListener("click", function () {
      arr.push(closures())
      arr.push(closures())

      content.innerHTML = arr.length
    });

  </script>
</body>

</html>

Как показано выше, это очень простой пример утечки памяти, связанной с неправильным использованием замыканий.

Давайте сначала дадим краткое введение, просто посмотрите наscriptКод JS может быть, во-первых, у нас естьclosuresфункция, это функция замыкания, простейшую функцию замыкания вам представлять не нужно, и тогда мыbuttonЭлемент привязан к событию щелчка, и каждый щелчок будет выполнять функцию закрытия 2 раза и выполнять результатpushв глобальный массивarr, потому что результат выполнения функции замыкания тоже является функцией и есть внутренний массив исходной функции замыканияtestцитаты, такarrКаждый элемент массива находится внутри замыкания, на которое он ссылается.testОбъекты массива не могут быть переработаны,arrКоличество элементов в массиве означает, сколько ссылок замыкания у нас есть, поэтому чем больше кликает программа,pushЧем больше страниц, тем больше потребление памяти и тем больше и больше страниц застревает.

Для облегчения последующего наблюдения в программе после каждого нажатия кнопки мы помещаем глобальный массивarrДанные о длине страницы обновляются до страницы, то есть начинаются с 0, а значение страницы увеличивается на 2 при каждом нажатии.

Конечно, это пример, который мы написали сами. Как Бог, мы знаем, чем это вызвано. Теперь забудьте об этом. Допустим, это одна из наших программ-проектов. Разработка завершена и сдана на тест. После нажатия кнопки , страница становилась все более и более вялой, и тут же возникал баг.

Как программисты, мы должны быть: «Хорошо обновлять следующую страницу, обновлять и обновлять, когда она зависает!!!»

В порядке. . . Товар и тест точно не сойдутся, давайте изменим, если пользователь первый. .

Хорошо, тогда измените его. Первый шаг – выяснить, что пошло не так и что вызвало его. Затем мы называем эту часть этапом решения проблемы.

Устранение неполадок

ChromeИнструменты разработчика — это то, что мы называем консолью браузера (Chrome Devtool) на самом деле очень мощная функция, она может помочь нам проанализировать различные вещи в программе, такие как производительность, безопасность, сеть и т. д., а также позволяет нам быстро найти источник проблемы, но большинство людей не знакомы с ее использованием. .

Поскольку в этой статье мы сосредоточимся на утечках памяти, мы предполагаем, что вышеприведенная программа проверила все элементы, кроме памяти, и проблемы нет, а затем приступаем к проверке памяти.

Сначала открываем браузер в режиме инкогнито, затем открываем программный код веб-страницы для проверки, а затем открываем консоль.Весь интерфейс программы очень прост, как показано ниже:

Затем мы находимPerformanceЭта панель, ранее называвшаясяTimeline,этоChrome DevtoolИнструмент, используемый для мониторинга показателей производительности, он может записывать и анализировать различные события, происходящие в жизненном цикле веб-сайта, мы можем использовать его для мониторинга и анализа различных ситуаций с производительностью в нашей программе, включая память, как показано ниже:

Далее запускаем операцию, обязательно подтверждаем проверку перед запускомMemoryОпция отмечена цифрой 5 выше, чтобы мы могли видеть анализ, связанный с памятью.

Нажмите, чтобы начать запись (отметка 1), чтобы войти в состояние записи, а затем сначала очистите GC, то есть щелкните маленькую корзину (отметка 6).

Затем нажмите сумасшедший на страницеclickНажмите кнопку 100 раз, и значение на странице должно быть 200. Мы снова щелкаем маленькую корзину, чтобы вручную запустить GC.

Сумасшедший щелчок снова на страницеclickНажмите кнопку 100 раз, значение на странице должно быть 400, затем остановите запись.

Давайте соблюдаем панель данных, генерируемую консолью, как показано ниже:

Два кружились красные выше, то естьHeapСоответствующая часть указывает на периодическое ухудшение памяти, что является просто нашей ситуацией с памятью.

Мы можем ясно видеть, что данные памяти показывают тенденцию к росту.Некоторые люди могут сказать, что GC не выполнялся в течение этого периода? Не волнуйтесь, помните ли вы, что мы щелкнули маленькую мусорную корзину на 200, то есть мы вручную запустили сборку мусора один раз, и мы можем узнать, где находится значение страницы в момент, когда значение страницы равно 200, через вышеизложенное моментальный снимок страницы, очень просто, просто наведите указатель мыши на снимок памяти, чтобы найти его, как показано ниже:

Видно, что даже если мы вручную выполнили операцию сборки мусора посередине, то память после очистки сильно не уменьшилась, из чего можно сделать вывод, что может иметь место утечка памяти в операции клика этой программы.

Хорошо, проблема проверена, затем следующий шаг — найти источник утечки.

Вы можете сказать, что, поскольку вы обнаружили проблему, это событие щелчка, вы можете просто исправить это?

Вы знаете, это простой пример, который мы написали, мы сразу видим, где проблема, но в реальном проекте может быть много операций в событии клика, и мы знаем только, что событие клика может привести к памяти утечки, Но мы не знаем, на каком этапе события клика возникла конкретная проблема, и мы не знаем более детальную причину и местонахождение.Все это требует от нас дальнейшего анализа и обнаружения.

Анализ и позиционирование

Далее приступаем к анализу и локализации источника утечки

Chrome Devtoolтакже предоставляет намMemoryПанель, которая может предоставить нам более подробную информацию, такую ​​как запись сведений о времени выполнения ЦП JS, отображение потребления памяти объектами JS и связанными узлами DOM, запись сведений о выделении памяти и т. д.

один из нихHeap ProfilingМожет записывать текущую память кучиheapГенерируется снимок объекта и создается файл описания объекта, в файле описания указаны все объекты, используемые текущей операцией JS, а также объем памяти, занимаемый этими объектами, иерархическая взаимосвязь ссылок и т.д. ., которые можно использовать для определения конкретной проблемы, вызвавшей проблему, причины и местонахождение.

Обратите внимание, неPerformanceтот что под панельюMemory, но сPerformanceпанель на одном уровнеMemoryпанели, как показано ниже:

Теперь значение страницы равно 400, конечно, вы можете обновить страницу, чтобы начать с 0, здесь мы выбираем продолжить операцию

Сначала нажмите на маленькую мусорную корзину (отметка 3), запустите GC и удалите ненужные вещи из памяти.

Нажмите, чтобы начать создание снимка (отметка 1), создайте первый снимок и выберите его, как показано ниже:

Кратко представим, что означает маленькая картинка выше:

в списке слеваSnapshot 1Представляет сгенерированный нами снимок 1, который является состоянием памяти в данный момент.

проверилSnapshot 1После этого находится правая таблица просмотра, в левом верхнем углу таблицы есть раскрывающийся список, в котором есть четыре значения.

  • Резюме: сгруппировано по конструктору, фиксирует объекты и их использование памяти, может быть понято как сводка памяти, используемая для отслеживания утечек памяти при обнаружении узлов DOM.
  • Сравнение: сравните разницу между моментальными снимками памяти до и после операции, проанализируйте освобождение памяти до и после операции и т. д., чтобы подтвердить наличие утечки памяти и ее причину.
  • Сдерживание: обнаружение определенного содержимого кучи, предоставление представления для просмотра структуры объекта, помощь в анализе ссылок на объекты, анализ замыканий и более глубокий анализ объектов.
  • Статистика: Просмотр статистики

Выпадающий список выберет для нас по умолчаниюSummary, поэтому в таблице ниже показана сводка памяти данных в снимке 1. Простое понимание состоит в том, что в момент создания снимка 1, что хранится в памяти, включая информацию, которая занимает память и так далее.

кратко понятьSummaryЧто представляют столбцы таблицы данных опций?

  • Конструктор: Отобразите все конструкторы, нажмите на каждый конструктор, чтобы просмотреть все объекты, созданные конструктором.
  • Расстояние: отображает расстояние до корневого узла по кратчайшему пути к узлу, ссылаясь на иерархию.
  • Мелкий размер: отображает память, занимаемую объектом, за исключением памяти, занимаемой другими объектами, на которые имеются внутренние ссылки.
  • Сохраненный размер: отображает общую память, занимаемую объектом, включая память, занимаемую другими объектами, на которые имеются внутренние ссылки.

Хорошо, на данный момент достаточно знать так много, давайте продолжим, сначала нажмите на маленькую корзину, чтобы вручную выполнить сборку мусора, а затем нажмите 1 на странице.clickкнопку и, наконец, снова нажмите кнопку «Создать снимок», чтобы создать наш второй снимок.

Чтобы быть точным, мы выполним несколько операций, а именно:

Сначала нажмите на небольшую корзину, чтобы вручную выполнить сборку мусора, а затем нажмите 2 на следующей странице.clickкнопку и, наконец, снова нажмите кнопку «Создать снимок», чтобы создать третий снимок.

Сначала нажмите на небольшую корзину, чтобы вручную выполнить GC, а затем нажмите на следующую страницу.clickкнопку и, наконец, снова нажмите кнопку «Создать снимок», чтобы создать четвертый снимок.

Затем мы выбираем снимок 2 и меняем раскрывающийся список над ним на значение по умолчанию.SummaryВозможность переключиться наcomparisonопция, которая предназначена для сравнения разницы в памяти между текущим снимком и предыдущим снимком, как показано ниже:

Давайте еще раз посмотрим на вариантыComparisonЧто представляет столбец таблицы после раскрывающегося списка? Вот несколько важных.

  • Новое: сколько объектов было создано
  • Удалено: сколько объектов было переработано
  • Дельта: количество вновь созданных объектов минус количество переработанных объектов

Эх, у нас тут немного, нам нужно сосредоточитьсяDelta, пока это положительное число, могут быть проблемы.Интимная консоль уже разобрала его для нас.Можем посмотреть на верхние по очереди.

Конечно, нам также нужно знать, что представляет собой каждая строка данных, и внимание смещается наConstructorВ этом столбце мы также сказали, что этот столбец является конструктором. Нажмите на каждый конструктор, чтобы просмотреть все объекты, созданные конструктором. Или мы должны сначала представить, что представляют собой общие конструкторы в этом столбце.

  • system, system/Context представляют собой какие-то ссылки, созданные самим движком и контекстом, на них не нужно обращать слишком много внимания, это не важно
  • закрытие представляет собой ссылку на объект в некотором закрытии функции
  • Также можно увидеть последовательность массива, строки, числа, регулярного выражения, то есть тип объекта, который ссылается на массив, строку, число или регулярное выражение.
  • HTMLDivElement, HTMLAnchorElement, DocumentFragment и т. д. на самом деле являются ссылками на элементы в вашем коде или ссылками на указанные объекты DOM.

Эй, это намного яснее, тогда мы можем сравнить это по очереди1->2 / 2->3 / 3->4Давайте посмотрим, где проблема.

Не волнуйся, подумай, что мы будем делать сейчас? Вам нужно нажать на снимок отдельно, а затем выбрать егоcomparison, затем посмотрите наDeltaЭлементы, перечисленные как положительные числа, анализируются снова.Эту операцию необходимо выполнить 3 раза, потому что у нас есть 4 снимка, и нам нужно сравнить и проанализировать 3 раза, а иногда может быть создано даже больше снимков для обеспечения точности.

Есть ли более простой способ? Да, мы можем напрямую выбрать снимок для сравнения.Так же есть всплывающее окно в правой части таблицы.Мы можем напрямую выбрать снимок для сравнения, а также отфильтровать некоторую бесполезную для нас информацию:

Давайте проделаем собственно операцию, выбираем снимок 2 слева, выбираем快照1и 快照2Проведен сравнительный анализ, и получены следующие результаты:

Как видите, после сравнения и фильтрации в списке осталось всего 4 отличия.

system/ContextНам не нужно заботиться.

closureВыше также сказано, что это ссылка на закрытие.Давайте щелкнем этот элемент, чтобы увидеть конкретную информацию:

можно увидеть,closureНиже приведены две ссылки, и это также подробно указано для нас в коде.21нажмите, чтобы выбрать одну из ссылок, и ниже отобразится более подробная информация.

Почему после расширения две ссылки? Помните, что мы генерируем快照2, сделал GC вручную и щелкнул один разclickкнопка, запускает событие клика, в событии клика мы выполняем иpushФункция закрытия вызывается дважды, поэтому записей 2.

Наконец мы видимarray, ссылка на массив здесь полностью из-за глобальной переменной массива в нашем коде случаяarrсуществование, ведь каждый щелчокpushЧто касается данных, то именно поэтому мы упоминали выше, почему мы должны уделять особое внимание использованию глобальных переменных и вовремя очищать их, потому что в этом случае, если вы не будете очищать эти глобальные переменные, эти глобальные переменные всегда будут быть в памяти до закрытия страницы.Может быть у вас есть сомнения, что два элемента в столбце конструктора являются массивами, на самом деле в этом нет ничего плохого, один представляетarrсам элемент представляет собой переменную массива, на которую ссылается замыканиеtest(Просмотрите приведенный выше код случая, если вы забыли), это также можно сделать с помощьюDistanceОпорные уровни представлены в столбцах для GET, один уровень равен 7, а другой уровень равен 8. Что касается местоположения кода утечки, вызванной массивом, мы также можем щелкнуть, чтобы развернуть и выбрать его справочную запись. Вы можете увидеть расположение кода в деталях. Та же операция, что и закрытие выше, здесь не будет продемонстрировано.

Эй, похоже, что известен конкретный источник утечки, давайте подтвердим это еще раз, выберите снимок 4 слева, выберите快照3и快照4Для сравнительного анализа перед созданием снимка 4 мы вручную выполнили сборку мусора и три раза щелкнули мышью.clickкнопка, если вышеуказанный вывод правильный, он должен быть таким же, как наш выше快照1и快照2Элементы данных результатов сравнения соответствуют 4 элементам, но внутренняя ссылка каждого элемента будет равна 6, потому что мы трижды нажимали кнопку перед этим снимком, и каждый раз, когда мы выполняем иpushЗапустите функцию закрытия дважды, чтобы увидеть результат:

image-20210707041841176

Ну тут вроде бы все понятно, всего проблем две, одна это утечка памяти из-за замыкания ссылочного массива в 21 строке кода, а вторая это глобальные переменные.arrУтечка памяти, вызванная постоянно растущим числом элементов.

Анализ и позиционирование выполнены успешно, перейдите к следующему шагу, восстановите и снова проверьте.

исправить проверку

Поскольку это временный случай, конкретной сцены нет, поэтому нет возможности использовать целевой метод для ее ремонта, поэтому этот шаг временно игнорируется, но нам все еще нужно решить его в проекте.

Например, глобальный объект продолжает увеличиваться Мы не можем избежать глобального объекта, но мы можем ограничить размер глобального объекта, и мы можем очистить его часть в соответствии со сценой.

Например, проблема ссылки на замыкание, не позволять ссылаться на нее или быть пустой после выполнения, о которых все упоминалось выше.

Короче говоря, все нужно выбрать решение в соответствии с конкретным сценарием, а после решения повторить описанный выше процесс устранения неполадок, чтобы увидеть память.

Три кусочка памяти

На самом деле, есть три основные проблемы с памятью во внешнем интерфейсе, которые я ласково называю тремя основными компонентами памяти:

内存泄漏Мы давно говорили, что объект больше не используется, но не рекультивирован, а память не освобождена, то есть утечки памяти.Если вы хотите этого избежать, вы должны избегать того, чтобы бесполезные данные имели ссылку отношения, то есть обращайте больше внимания на общие, о которых мы упоминали выше, в случае утечки памяти.

内存膨胀То есть за короткий промежуток времени занятость памяти быстро растет и достигает пика.Если вы хотите избежать необходимости использования технических средств для уменьшения занятости памяти.

频繁 GCСборка мусора с одним и тем же именем выполняется очень часто, обычно это происходит, когда частое использование больших временных переменных приводит к тому, что пространство нового поколения заполняется очень быстро, а сборка мусора запускается каждый раз, когда новое поколение заполняется, и часто GC также приведет к странице застряла. Если вы хотите избежать этого, не создавайте слишком много временных переменных, потому что временные переменные будут переработаны, когда они не используются. Это конфликтует с нашей утечкой памяти, которая позволяет избежать использования глобальные переменные На самом деле, пока вы понимаете, что слишком много — это нормально.

Наконец

Если ваша программа зависла, и вы не можете найти причину, вы можете попробовать метод устранения неполадок, описанный в этой статье.Возможно, это вызвано утечкой памяти.В то же время, это также момент, на который мы должны обратить особое внимание. при оптимизации страницы.

Вот и все на сегодня, вы ПОНЯЛИ? Добро пожаловать на ошибки! Писать не просто, двигайте ручонками, чтобы поставить лайк, собирать прах - табу 👊

Также приглашаем подписаться«Хардкор JS»Колонка, углубленное введение в небольшие знания JS, расскажет вам о JavaScript, которого вы не знаете! ! !