Подтвердить что ты не робот

Как создать утечку памяти в JavaScript?

Я хотел бы понять, какой код вызывает утечку памяти в JavaScript и создал script ниже. Однако при запуске script в Safari 6.0.4 в OS X потребление памяти, отображаемое в мониторе активности, действительно не увеличивается.

Что-то не так с моим script или это уже не проблема в современных браузерах?

<html>
<body>
</body>
<script>
var i, el;

function attachAlert(element) {
    element.onclick = function() { alert(element.innerHTML); };
}

for (i = 0; i < 1000000; i++) {
    el = document.createElement('div');
    el.innerHTML = i;
    attachAlert(el);
}
</script>
</html>

script основан на разделе "Закрытие" руководства по стилю JavaScript JavaScript: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml?showone=Closures#Closures

EDIT: ошибка, которая вызвала утечку вышеуказанного кода, по-видимому, исправлена: http://jibbering.com/faq/notes/closures/#clMem

Но мой вопрос остается: может ли кто-нибудь дать реалистичный пример кода JavaScript, который утечки памяти в современных браузерах?

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

4b9b3361

Ответ 1

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

В принципе, прохождение вашего кода:

  • создать элемент (el)
  • создать новую функцию, которая ссылается на Элемент
  • установите для функции значение onclick этого элемента
  • перезаписать элемент новым элементом

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

У кого-то может быть более технический пример, но в основе моего понимания сборщика мусора javascript.

Изменить: здесь одна из многих возможностей для протекающей версии вашего script:

<html>
<body>
</body>
<script>
var i, el;

var createdElements = {};
var events = [];

function attachAlert(element) {
    element.onclick = function() { alert(element.innerHTML); };
}

function reallyBadAttachAlert(element) {
    return function() { alert(element.innerHTML); };
}

for (i = 0; i < 1000000; i++) {
    el = document.createElement('div');
    el.innerHTML = i;

    /** posibility one: you're storing the element somewhere **/
    attachAlert(el);
    createdElements['div' + i] = el; 

    /** posibility two: you're storing the callbacks somewhere **/
    event = reallyBadAttachAlert(el);
    events.push(event);
    el.onclick = event;

}
</script>
</html>

Итак, для # 1 вы просто храните ссылку на этот элемент где-то. Не имеет значения, что вы никогда не будете использовать его, потому что эта ссылка делается в объекте, элемент и его обратные вызовы никогда не исчезнут (или, по крайней мере, до тех пор, пока вы не удалите элемент из объекта). Для возможности № 2 вы можете где-то хранить события. Поскольку доступ к событию можно получить (т.е. Выполнив events[10]();), хотя элемент нигде не найден, он все еще ссылается на событие.. поэтому элемент останется как в памяти, так и в событии, пока он не будет удален из массив.

Ответ 2

обновление: вот очень простой пример, основанный на сценарии кэширования в презентации ввода/вывода Google:

/*
This is an example of a memory leak. A new property is added to the cache
object 10 times/second. The value of performance.memory.usedJSHeapSize
steadily increases.

Since the value of cache[key] is easy to recalculate, we might want to free
that memory if it becomes low. However, there is no way to do that...

Another method to manually clear the cache could be added, but manually
adding memory checks adds a lot of extra code and overhead. It would be
nice if we could clear the cache automatically only when memory became low.

Thus the solution presented at Google I/O!
*/

(function(w){
    var cache = {}
    function getCachedThing(key) {
        if(!(key in cache)) {
            cache[key] = key;
        }
        return cache[key];
    }

    var i = 0;
    setInterval(function() {
        getCachedThing(i++);
    }, 100);
    w.getCachedThing = getCachedThing
})(window);

Поскольку usedJSHeapSize не обновляется при открытии страницы из локальной файловой системы, вы можете не увидеть увеличения использования памяти. В этом случае я разместил этот код для вас здесь: https://memory-leak.surge.sh/example-for-waterfr


В этой презентации Google I/O'19 приводятся примеры реальных утечек памяти, а также стратегии их устранения:

  • Метод getImageCached() возвращает ссылку на объект, также кэшируя локальную ссылку. Даже если эта ссылка выходит за пределы области применения метода, указанная память не может быть собрана сборщиком мусора, поскольку внутри реализации getImageCached() все еще существует сильная ссылка. В идеале кэшированная ссылка будет подходящей для сборки мусора, если объем памяти будет слишком низким. (Не совсем утечка памяти, а ситуация, когда есть память, которая может быть освобождена за счет повторного выполнения дорогостоящих операций).
  • Утечка # 1: ссылка на кэшированное изображение. Решено с помощью слабых ссылок внутри getImageCached().
  • Утечка # 2: строковые ключи внутри кеша (объект Map). Решено с помощью нового FinalizationGroup API.

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

В более общем смысле, "настоящие" утечки памяти JS вызваны нежелательными ссылками (на объекты, которые никогда не будут использоваться снова). Обычно это ошибки в коде JS. В этой статье рассказывается о четырех распространенных способах утечки памяти в JS:

  1. Случайные глобальные переменные
  2. Забытые таймеры/обратные вызовы
  3. Вне ссылок DOM
  4. Затворы

Интересный вид утечки памяти JavaScript описывает, как замыкания вызывали утечку памяти в популярной инфраструктуре MeteorJS.

Ответ 3

Если все, что вам нужно, это создать утечку памяти, то самый простой способ для IMO - создать экземпляр TypedArray, поскольку он собирает фиксированный размер памяти и переживает любые ссылки. Например, создание Float64Array с 2^27 элементами потребляет 1 ГБ (1 Гибибайт) памяти, поскольку для каждого элемента требуется 8 байт.

Запустите консоль и просто напишите это:

new Float64Array(Math.po2(2, 27))

Ответ 4

Я попытался сделать что-то подобное и вытащил исключение из памяти.

const test = (array) => {
  array.push((new Array(1000000)).fill('test'));
};

const testArray = [];

for(let i = 0; i <= 1000; i++) {
  test(testArray);
}