LINUX.ORG.RU

Утечки памяти в языках со сборкой мусора

 ,


2

2

Хотелось бы увидеть интересных примеров, когда вполне адекватный код может привести к утечкам памяти на разных языках со сборкой мусора и «умными» ссылками (напр. PHP, Python, Ruby, Java, JS, ...).

ЕМНИП, на Perl это делается так:

use strict;
use warnings;

sub leak {
    my $foo;
    my $bar = { foo => $foo };
    $foo->{bar} = $bar;
}

# можно циклом
leak();

Соль в том, что используется метод подсчёта ссылок. Но так как $foo и $bar ссылаются друг на друга, «сборщик мусора» не сможет корректно высвободить память. Возможно уже починили

★★★★★

Последнее исправление: KennyMinigun (всего исправлений: 1)

D JS этого нет. Раньше было, но в современных реализациях используется mark-and-sweep, а проблема циклических ссылок имеет место только в реализации GC на основе подсчета ссылок (в JS раньше это было).Вообще, в JS в плане памяти наследование делегированием рулит.

terminator-101
()

У меня была утечка процессов в эрланге, ну и вследствие немного и памяти (быстрее в лимит процессов упёрся), если интересно, могу рассказать из-за чего.

anonymous
()
Ответ на: комментарий от terminator-101

реализации GC на основе подсчета ссылок

Обычно такие «реализации GC» реализациями GC не называют.

Возможно уже починили

Ты как будто Америку открыл, в большинстве (если не во всех) перечисленных тобой языках эту «проблему» давно решили.

В языках со сборкой мусора именно эта ситуация (циклические структуры данных) ни к каким утечкам не приводит.

korvin_ ★★★★★
()

язычки, как правило адекватны, текут поделия

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

Интереснее не языки, а примеры основных утечек, сегфолтов и т.п.

Например часто бывает так, что в деструкторах обращаются к несуществующим сущностям, а мастерство быдлокодинга приводит к тому, что ресурсы не высвобождаются.

Протечки в виде процессов интересное явление - так-что расскажите конечно. Страхово, когда процесс не высвобождает ресусры.

Всё течет, всё изменяется...

;)

anonymous
()

интересных примеров

Кто студенческий код видел, тот примерами не интересуется.

Deleted
()
Ответ на: комментарий от anonymous

Да, конечно, этот тред именно для этого

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от korvin_

Ты как будто Америку открыл, в большинстве (если не во всех) перечисленных тобой языках эту «проблему» давно решили.
В языках со сборкой мусора именно эта ситуация (циклические структуры данных) ни к каким утечкам не приводит.

Круто. А кулстори где?

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Например часто бывает так, что в деструкторах обращаются к несуществующим сущностям, а мастерство быдлокодинга приводит к тому, что ресурсы не высвобождаются.

а зачем они вообще из деструкторов обращаются к чемуто кроме внутренних данных, по отношению к объекту деструктора?

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

user_id_68054 ★★★★★
()
Ответ на: комментарий от user_id_68054

и здесь вот прямо так и хочется сказать спасибо тем, кто запретил в йаве руками делать finalize(), хотя признаюсь, иногда так и тянет. и вот за это и спасибо)

anonymous
()

Везде возможны проблемы подобного вида:

import java.util.ArrayList;

public class leak {
        public static void main(String[] args) {
                ArrayList<Object> list = new ArrayList<>();
                while(true) {
                        list.add(new Object());
                }
        }
}

proud_anon ★★★★★
()
Ответ на: комментарий от terminator-101

JS этого нет

проорал в голосину в три часа ночи на пол города

В ДЖС НЕТ УТЕЧК

ОКЕЙ

тогда сделай полностью динамическую веб страницу(с дозагрузкой всего),тоесть страница без перезагрузки,переход по ссылкам-загрузка новых фрагментов и показ(убирая старые)

или банальная «галерея»,или даже ГУГЛ страница поиска...

на любой галерее с 100+фотками(поиск картинок гугла даже прокручивая пару экранов вниз)-эжу все потечет,тоесть расход памяти на каждые следующие «объекты» будет расти пропорционально имеющимся(и расти вплоть до того что пролистав пару десятков экранов вниз,каждая следущая прокрутка будет сжирать столькоже сколько уже занято-тоесть сотни гигабайт и больше)

и это еще маленьная проблема
я тебе открою страшную правду
в ДЖС НЕВОЗМОЖНО УДАЛЯТЬ ОБЪЕКТЫ,все загруженное будет в пямыти до перезагрузки страницы

мне даже вспоминать противно как я плевался с ДЖС реализаций-это такой кошмар,что сложнее хело ворда и вечной перезагрузки в вебе не сделать(я молчу про хтмл5,даже пара минут работы html5 активной страницы сжирает десятки гигабайт)

anonymous
()
Ответ на: комментарий от anonymous

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

тщательно котролировать работу таймеров, где можно воздерживаться от них

anonymous
()
Ответ на: комментарий от anonymous

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

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

как оно работает:
библиотека JS(в смысле jquery или подобное,или свой банальный костыль) имеет класс/функции объекты вобщем
ты создаешь(вызываешь) объект «библиотека» из своего кода(на странице)
объект «библиотека» загружает и выполняет операции с данными внутри себя(своими функциями,что логично)
возвращяет тебе готовый обрабоаный объект(результат)
и вот вопрос-очищается ли память целого «объект-библиотека» используемого тобой,при следующем вызове
тоесть у тебя было
lib1=<класс библиотека>
f1=<класс библиотека,функция или переменая(грубо говоря «статик» из класса)>
lib1=null
что станет с данными из f1(которые внутри lib1(косвенно))
и теперь представь что f1 находиться внутри <класс библиотека>,тоесть изнутри вызывается собственный метод через создание своего класса и «статик»(которого в JS нет,если не понял),тоесть неявный проброс свойств/методов/объектов из созданного экземпляра объекта
(я описал пример,рабочих примеров нет,занимался веб трешем несколько лет назад и бросил)

про утечку памяти в браузерах ты не понял,или я неправильно рассказал-память течет из за не удаляемых объектов «рендеринга» внутри браузера(тоесть ты создал div со стилем внутри картинка-это все отрендерилось,ты убрал с экрана-но в памяти оно осталось,никакое присвоение null не поможет-тебе банально нечему присваивать,так как ты «неявным» образом работаешь с рендером(просто передавая ему новые метаданные)-это основная утечна памяти,на фоне которой пложение сотен таймеров на объект или прогрессия размерности массива «какойто инфы»(все внутри логики джаваскрипта этой страницы естетвенно)-ничтожно,хоть и жрет ЦП

и кстати да-повесив таймер на «объект» на странице,и скрыв этот объект-таймер никуда не денеться и продолжит работать,тоесть какя описал выше-загрузив 100 картинок на каждом по обработчику который создал по экземпляру класса jquery(к примеру) для каждой из картинок...сотни пямяти

anonymous
()
Ответ на: комментарий от korvin_

Обычно такие «реализации GC» реализациями GC не называют.

Лорчую этого адеквата. Вот откроешь нормальную книгу, а там: «методы автоматического управления памятью: подсчёт ссылок, сборка мусора etc»

Тутошние аналитики изобрели сборку мусора с помощью подсчёта ссылок.

kike
()
Ответ на: комментарий от anonymous

100 картинок на каждом по обработчику

man делегирование событий

ritsufag ★★★★★
()
Ответ на: комментарий от korvin_

реализации GC на основе подсчета ссылок

Обычно такие «реализации GC» реализациями GC не называют.

ну просто подсчёт неправильно реализован, т.е. A→B и B→A, при этом в каждой переменной ссылка ==1. А надо A→C и B→C, причём это C невидимо из кода, и именно там лежит счётчик, который ==2.

emulek
()
Ответ на: комментарий от user_id_68054

а зачем они вообще из деструкторов обращаются к чемуто кроме внутренних данных, по отношению к объекту деструктора?

«не внутренние» не обязательно «внешние». Смотри, у тебя класс C является наследником A и B. Скажи, при удалении C, в каком порядке удаляться A и B? Вот мудрые C++сники придумали для того специальный костыль. А все остальные радуются, что в их стандарте «нет UB». А оно есть, просто в их стандарте про это не слова.

emulek
()

Возможно уже починили

Не починят. Это не считается за багу или ошибку компилятора. Зато есть weak reference.

gh0stwizard ★★★★★
()
Ответ на: комментарий от user_id_68054

а зачем они вообще из деструкторов обращаются к чемуто кроме внутренних данных, по отношению к объекту деструктора?

1) связанные списки

2) за давностью лет забыл, но у меня получилось сделать совершенно неочевидную циклическую ссылку работая с полями класса. Ссылка произошла внутри кишков питона. Ну, в том смысле что при передаче bound-метода передаётся и «указатель» на self (замыкание), а у self есть «указатель» на метод... Короче, суть понятна.

true_admin ★★★★★
()

Самый простой способ - не чистить глобальный или долгоживущий контейнер от ненужных объектов.

tailgunner ★★★★★
()
Ответ на: комментарий от KennyMinigun

А кулстори где?

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

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

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

implement Cycle;

include "sys.m";
include "draw.m";

sys: Sys;

Cycle: module
{
	init: fn(nil: ref Draw->Context, nil: list of string);
};

Foo: adt
{
	bar: ref Bar;
};

Bar: adt
{
	foo: ref Foo;
};

leak(bar: ref Bar): ref Foo
{
	foo := ref Foo;
	foo.bar = bar;
	return foo;
}

init(nil: ref Draw->Context, nil: list of string)
{
	sys = load Sys Sys->PATH;
	bar := ref Bar;
	bar.foo = leak(bar);
	sys->print("leaked\n");
}
cycle.b:26: cannot assign to 'foo.bar' because field 'bar' of 'foo' could complete a cycle to 'foo'
cycle.b:34: cannot assign to 'bar.foo' because field 'foo' of 'bar' could complete a cycle to 'bar'
94 "Limbo":fail:errors

11.1. Forward referencing

...

These restrictions suffice to prevent the creation of circular data structures. Limbo implementations guarantee to destroy all data objects not involved in such circularity immediately after they become non-referenced by active tasks, whether because their names go out of scope or because they are assigned new values. This property has visible effect because certain system resources, like windows and file descriptors, can be seen outside the program. In particular, if a reference to such a resource is held only within an adt, then that resource too is destroyed when the adt is.

Для создания циклических структур нужно явно помечать ссылку словом cyclic:

Foo: adt
{
	bar: cyclic ref Bar;
};

Bar: adt
{
	foo: cyclic ref Foo;
};

With the use of cyclic, circular data structures can be created. When they become unreferenced except by themselves, they will be garbage-collected eventually, but not instantly.

korvin_ ★★★★★
()
Ответ на: комментарий от tailgunner

Подсчет ссылок - это одна из техник сборки мусора.

Я бы назвал подсчет ссылок — одной из техник управления временем жизни объектов, на ряду со сборкой мусора.

korvin_ ★★★★★
()

Из своего. В java-классе сделал тред, выводящий отладочную информацию. Когда он стал ненужен, я thread.start() закомментировал. Потом долго ловил OutOfMemoryError, пока не вычитал на stackoverflow, что созданный, но не запущенный тред не собирается сборщиком мусора, а висит в памяти. В моем случае он держал this-ссылку на громоздкий внешний класс.

crowbar
()
Ответ на: комментарий от emulek

Смотри, у тебя класс C является наследником A и B. Скажи, при удалении C, в каком порядке удаляться A и B?

не очень понимаю о чём ты говоришь, так как я-то ведь говорил про объекты, а ты говоришь про классы. :)

вот удаляется объект, и вызывается функция-деструктор его класса (будем считать не нашлось ни однго умника, кто забыл сделать деструктор виртуальным)..

а как это понять — удаляется класс?

user_id_68054 ★★★★★
()

Да элементарно - любой hash map, в который невозбранно добавляют ссылки на всякое говно. Дебажить такое очень бывает непросто.

anonymous
()
Ответ на: комментарий от user_id_68054

не очень понимаю о чём ты говоришь, так как я-то ведь говорил про объекты

что тут непонятного? Конечно new рожает объекты. Какого-то класса. Вот и я про объекты. Просто так короче, я думал ты поймёшь мою мысль.

s/класс C/объект типа(описанный в классе) C/

кто забыл сделать деструктор виртуальным

ты забыл, что не все тут пишут на C++.

а как это понять — удаляется класс?

удаляется естественно объект.

emulek
()
Ответ на: комментарий от emulek

ты забыл, что не все тут пишут на C++.

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

(в C++ деструктор можно делать на-усмотрение-программиста виртуальным, или НЕ виртуальным.. хотя я не понимаю зачем его делать не виртуальным :))

> а как это понять — удаляется класс?

удаляется естественно объект.

имеется ввиду определённый-порядок вызовов функций деструкторов относительно всей цепочки наследования?

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

да.. прикольно это! :-) спасиб за пример

user_id_68054 ★★★★★
()
Ответ на: комментарий от anonymous

Протечки в виде процессов интересное явление - так-что расскажите конечно. Страхово, когда процесс не высвобождает ресусры.

Ну процессы в Эрланге это совсем не тоже, что процессы системы или java threads, это скорей lwp.

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

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

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

Решением является дополнительный вызов связи процессом А, оно практически бесплатно, если процессы локальные.

anonymous
()
Ответ на: комментарий от anonymous

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

Ну допустим.

Обычно такими вещами занимается супервизор - там все шоколадно, только не лениться писать модули sup.

anonymous
()
Ответ на: комментарий от anonymous

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

Не, причина, что процесс умирает до того, как флажок выставляется.

Обычно такими вещами занимается супервизор - там все шоколадно, только не лениться писать модули sup.

По идее да, можно было переложить это всё дело на отдельный контрольный процесс, но это был бы нестандартный, а свой супервизор. Запускать для каждых спареных процессов(их под сотни тысяч) ещё отдельного супервизора, как-то жутко неоптимально мне кажется.

anonymous
()
Ответ на: комментарий от anonymous

Супервизор для этого и предназначен. Не нужно изобретать велосипеды - старина Джо Армстронг уже все придумал. Примеров, где используются sup-модули навалом. Они все рассчитаны на большое число процессов и как раз нужны там, где много.

Когда не синхронно выставляется флаг - это состояния и гонки.

См. gen_fsm, асинхронные обработчики, очереди.

anonymous
()
Ответ на: комментарий от CARS

Могучей не бывает. У sbcl есть generational GC, могущий потоки и прочее. А вообще, кастани mv

hey-propagandist
()
Ответ на: комментарий от emulek

где ты видишь проблему?

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

proud_anon ★★★★★
()
Ответ на: комментарий от proud_anon

когда забывают о существовании некой коллекции

а, понял. Осталось рассказать всем нам, где взять libastral.so, которая подскажет, забыли мы коллекцию, или она ещё нам понадобиться в будущем.

emulek
()
Ответ на: комментарий от user_id_68054

В C++ не может, ибо в деструкторе виртуальные вызовы относятся к классу деструктора. Т.е. нельзя вызвать метод потомка, поскольку потомок уже разрушен.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.