JavaScript

Утечка памяти в С и JavaScript: почему происходит и как предотвратить/исправить

Lorem ipsum dolor

Утечка памяти — это процесс, при котором память, не используемая приложением, не возвращается обратно в операционную систему или кучу. Утечка памяти провоцирует разные проблемы, например:

  • зависание приложения;

  • проблемы с работой пользовательского интерфейса;

  • ухудшение работы с другими приложениями, не связанными с утечкой памяти;

  • и др.

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

  • инструменты ручного управления — при помощи них разработчик может напрямую воздействовать на память;

  • инструменты автоматического управления — это «сборщики мусора», которые предусмотрены во многих языках.

Утечка памяти — нередкий процесс, которой свойственен программам на многих языках программирования. Сегодня мы обсудим, как выглядит утечка памяти в JavaScript и С/С++.

Утечка памяти в JavaScript (JS)

Утечка в JS контролируется сборщиком мусора, который периодически «прочесывает» работу приложения и определяет, какие фрагменты памяти задействованы приложением, а какие нет. Незадействованную память он может «удалить», возвратив ее операционной системе. Однако проблема заключается в том, что сборщик мусора не способен определить, будет ли неиспользуемая память нужна приложению в ближайшем будущем. Есть вероятность, что память не нужно возвращать, так как она будет использоваться приложением. Такие вещи способен различать только разработчик. Поэтому работа сборщика мусора не лишена проблем.

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

Чтобы утечка памяти в JS не приносила вам проблем, важно знать, из-за чего она может образоваться.

Утечка в JS: распространенные виды

Утечка памяти в JS может возникнуть из-за:

  • случайной глобальной переменной;

  • забытого таймера;

  • слушателей событий;

  • замыкания.

Случайная глобальная переменная

Случайные глобальные переменный получаются в JavaScript из-за его «нестрогости». Из-за этого необъявленная переменная может быть обработана. Ее обработка приводит к созданию глобальной переменной. Смотрим код:

function MyFunction(arg) {

    bar = "будет скрытой глобальной переменной";

}

Если нужно было, чтобы переменная «bar» была только внутри функции «MyFunction», тогда нужно было объявить ее через «var».

Также глобальная переменная может быть вызвана, если неправильно применить слово «this». Смотрим код:

function MyFunction () {

    this.bar = "это потенциально глобальная переменная";

 

Чтобы ограничить себя от подобных ошибок, которые могут быть просто незамеченными, но впоследствии вызвать много проблем, нужно использовать выражение «use strict»в начале JS-скриптов. Это выражение включает строгий режим JavaScript, при котором возникновение случайных глобальных переменных исключено.

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

Когда в коде присутствуют «setTimeout» или «setInterval», тогда присутствует риск, что возникнет утечка JS. Эти два термина ссылаются на некий объект. Пока они на него ссылаются, память объекта не может быть почищена сборщиком мусора. Иногда такие объекты хранят большие объемы информации, а очищенными быть не могут. 

Как это выглядит в коде:

var myResource = getData();

setInterval(function() {

    var number = document.getElementById('Nunber');

    if(nunber) {

        // Выполняем что-нибудь с number и myResource.

        number.innerHTML = JSON.stringify(myResource));

    }

}, 1000);

 

В этом случае «myResource» несет в себе большой объем информации, но не может быть очищенным.

Для предотвращения таких ситуаций важно:

  • осознавать, на какие объекты ссылается обратный вызов таймера;

  • вовремя отменять вызов таймера методами «clearTimeout» и «clearInterval».

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

Слушатель событий в активном режиме предотвращает сбор мусора с переменных, находящихся в его области. Такой слушатель добавляется методом «addEventListener()» и работает, пока:

  • не удалить его методом «removeEventListener()»;

  • не удалить элемент, связанный со слушателем.

Важно учитывать эти моменты при работе со слушателями событий в JavaScript.

Замыкания

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

function myFunction() {

    const potentiallyLargeArray = [];

    return function inner() {

        potentiallyLargeArray.push('Привет'); // функция inner замыкается на переменной potentiallyLargeArray 

        console.log('Привет');

    };

};

 

const speakHello = myFunction(); // несет в себе определение функции inner

 

function repeat(fun, number) {

    for (let i = 0; i < number; i++){

        fun();

    }

}

 

repeat(speakHello, 10); // каждое определение speakHello помещает еще один элемент 'Привет' в массив potentiallyLargeArray 

 // попробуйте представить себе, что будет при определении repeat(speakHello, 100000)

В примере выше массив «potentiallyHugeArray» будет бесконечно расширять размер в зависимости от того, сколько раз будет вызвана функция «inner()».

Замыкания и JavaScript это две неотъемлемые части, поэтому они всегда будут в JS-программе. Чтобы утечка в JS происходила реже из-за замыкания, разработчику важно:

  • определить вероятное замыкание со всеми объектами, которые оно удерживает, а также когда оно было создано;

  • определить вероятную продолжительность замыкания.

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

На самом деле, JavaScript часто преподносят как легкий язык программирования, который могут смело изучать новички. Это так, язык изучить несложно. Сложнее решать вот такие задачи, как утечка памяти в JS. Стандартных подходов здесь нет, есть только рекомендации «бывалых», но и они не всегда помогают, так как многое зависит от разрабатываемой программы. Мы описали всего 4 проблемы, создающие утечку памяти в JS, но их несколько больше. Мы обязательно подробнее остановимся на этой теме в следующих статьях.

Утечка памяти в С/С++ 

Утечка памяти в С/С++ достаточно серьезная проблема для программиста. Суть в том, что утечки памяти в С-программах редко обнаруживаются на начальных этапах. Чаще всего программист замечает утечку только после длительной работы, когда она уже исчерпала лимит памяти, выделенный для нее, и зависает или аварийно завершает работу.

Видов возникновения утечек в С очень много, чаще всего они связаны с невнимательностью программиста. Например, вот простая программа с утечкой:

#include <iostream>

 

int main() {

  int* х = new int[2];

  std::cout << х;

  // программист забыл указать оператор delete[] х для массива "х";

}

 

Вот еще один пример классической утечки памяти в С из-за недостижимого фрагмента памяти, в частности программист забыл вызвать «free()»:

void leak_memory() {

  char *leaked = malloc(4096);

  use_a_buffer(leaked);

  /* Вот тут программист не вызвал free() */

}

 

Утечка С: решение

Самое важное, что у С/С++ есть собственный инструмент для обнаружения утечек памяти. То есть от возникновения утечки памяти в С-программе не застрахован никто. Но обязанность каждого С-программиста проверять собственные программы на наличие утечек еще в процессе работы над программой.

Инструментами для выявления утечек памяти в С являются:

  • отладчик С/С++;

  • и отладочные функции библиотеки CTR.

Чтобы использовать возможности библиотеки CTR, нужно вставить всего 3 строчки в начало своей С-программы:

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

 

В начале программы пишите 3 строчки, а в конце программы нужно написать макрос «_CrtDumpMemoryLeaks();», который будет выводить информацию о состоянии памяти в текущей программе.

Для продвинутых IDE есть плагины для поиска утечек памяти. Например, если разработчик работает с Microsoft Visual Studio, тогда он может использовать плагин Visual Leak Detector, который обнаружит все утечки и выведет их на экран.

Заключение

Утечка памяти в С, JavaScript или другом языке программирования всегда является причиной дополнительных проблем с программой. Писать без утечек пока не может никто. Но находить и устранять их можно. Где-то благодаря внимательности программиста и «строгому режиму» языка, а где-то при помощи дополнительного инструмента.

Схожие статьи

Функциональное программирование в Java: определение, паттерны и применение
JavaScript

Функциональное программирование в Java: определение, паттерны и применение

Вопросы и ответы по Java: подготовка к собеседованию
JavaScript

Вопросы и ответы по Java: подготовка к собеседованию

Учебный курс по React, часть 12: практикум, третий этап работы над TODO-приложением
JavaScript

Учебный курс по React, часть 12: практикум, третий этап работы над TODO-приложением

Что значит JavaScript Error: учимся дебажить JavaScript на примерах
JavaScript

Что значит JavaScript Error: учимся дебажить JavaScript на примерах