Re: Помогите разобраться с памятью
От: Kalina9001  
Дата: 27.07.09 11:14
Оценка: +1
Возми какой нибудь профайлер(к примеру dotTrace) и посмотри, что там в памяти остается.
... << RSDN@Home 1.2.0 alpha 4 rev. 1231>>
Помогите разобраться с памятью
От: Вульфович Филипп  
Дата: 27.07.09 10:35
Оценка:
Пришел на работу программистом. В наследство досталось программа, написанная другим человеком на C#.
Программа — база данных на MS SQL 2000 cервере, уже написано много логики, переписать все с нуля нереально долго. Состоит из кучи форм, на которых поставлены датасеты, датаадаптеры и bindingsource. Каждая форма сама открывает connection, лезет в базу данных и загружает все данные (через методы адаптеров Fill) после окончания сохраняет данные (через методы адаптеров Update). Работает медленно, но хуже всего, что теряется память. Если раз 10-20 открыть и закрыть форму — диспетчер задач показывает что память растет. Иногда конечно происходит небольшой скачок вниз (может быть работает сборщик мусора), но в среднем память все равно увеличивается.
Поскольку это C# я никогда не освобождаю объекты, но я новичок в .Net, до этого работал много на Delphi. В Delphi освобождать объекты необходимо, но в C# я всегда думал, что с этим справится сборщик мусора. Может я жестоко ошибаюсь? Объем памяти при открытии программы 32 Мб, через час может достигать 200 Мб и больше. Пользователи жалуются что через несколько часов компьютер намертво подвисает и приходится перезагружать.

Пробовал в нескольких местах вставлять GC.Collect но это не помогает. Помогите, что делать?
Может быть надо везде освобождать объекты явно при закрытии формы? Или есть какой-то другой способ бороться с утечкой памяти.

Вульфович Филипп
Re: Помогите разобраться с памятью
От: terixg  
Дата: 27.07.09 11:07
Оценка:
ВФ>Пробовал в нескольких местах вставлять GC.Collect но это не помогает. Помогите, что делать?
ВФ>Может быть надо везде освобождать объекты явно при закрытии формы? Или есть какой-то другой способ бороться с утечкой памяти.

Такая проблема у меня была. Помог инет . Надо делать вызов метода Clear() у всех объектов DataTable и DataSet после того как они тебе не нужны (при закрытии формы, например).
Re[2]: Помогите разобраться с памятью
От: terixg  
Дата: 27.07.09 11:38
Оценка:
Вот здесь что-то похожее решается:
http://www.gotdotnet.ru/Forums/Data/591962.aspx
Re: Помогите разобраться с памятью
От: fmiracle  
Дата: 27.07.09 11:52
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Поскольку это C# я никогда не освобождаю объекты, но я новичок в .Net, до этого работал много на Delphi. В Delphi освобождать объекты необходимо, но в C# я всегда думал, что с этим справится сборщик мусора.


Он справится, если на объект нет никаких ссылок. Судя по всему, остаются ссылки на "мертвые" объекты.

Это могут быть как явные ссылки, так и неявные — например, в виде объектов, на события от которых кто-то подписан (например, основная ворма или само приложение). Во втором случае найти их не так-то просто.
Сборщик мусора при этом считает их "нужными" объектами и не удаляет.

вызов GC.Collect при этом не поможет.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re: Помогите разобраться с памятью
От: Smarty Россия  
Дата: 27.07.09 17:25
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Поскольку это C# я никогда не освобождаю объекты, но я новичок в .Net, до этого работал много на Delphi. В Delphi освобождать объекты необходимо, но в C# я всегда думал, что с этим справится сборщик мусора. Может я жестоко ошибаюсь? Объем памяти при открытии программы 32 Мб, через час может достигать 200 Мб и больше. Пользователи жалуются что через несколько часов компьютер намертво подвисает и приходится перезагружать.


Наверно идиотский вопрос, но все же — компиляция описываемой программы ТОЧНО проведена в релиз-варианте? Не дебаг?
Re[2]: Помогите разобраться с памятью
От: Вульфович Филипп  
Дата: 28.07.09 11:59
Оценка:
Здравствуйте, Kalina9001, Вы писали:

K>Возми какой нибудь профайлер(к примеру dotTrace) и посмотри, что там в памяти остается.


Скачал, установил профайлер. У профайлера dotTracе есть возможность отслеживать разницу между двумя
слепками. Так вот я делаю следующее:

1) Mark Memory
2) Создаю форму с датасетом и таблицами
3) Сразу же закрываю ее
4) Get Snapshot

И вот что интересно: форма после того, как я ее закрыл все еще жива! По крайней мере она (вместе со всеми БД-объектами) видна во вкладке "Show live objects", а во вкладке "Show dead objects" ее нет.

Как сделать так, чтобы все сходилось как в бухгалтерии: все объекты созданные при создании формы , при закрытии формы освобождались. Я хочу добиться, чтобы профайлер показывал все мои объекты во вкладке "Garbage objects", т.е. объекты, созданные и тут же уничтоженные.
Re[3]: Помогите разобраться с памятью
От: Мишень-сан  
Дата: 28.07.09 12:09
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Здравствуйте, Kalina9001, Вы писали:


K>>Возми какой нибудь профайлер(к примеру dotTrace) и посмотри, что там в памяти остается.


ВФ>Скачал, установил профайлер. У профайлера dotTracе есть возможность отслеживать разницу между двумя

ВФ>слепками. Так вот я делаю следующее:

ВФ>1) Mark Memory

ВФ>2) Создаю форму с датасетом и таблицами
ВФ>3) Сразу же закрываю ее
ВФ>4) Get Snapshot

ВФ>И вот что интересно: форма после того, как я ее закрыл все еще жива! По крайней мере она (вместе со всеми БД-объектами) видна во вкладке "Show live objects", а во вкладке "Show dead objects" ее нет.


ВФ>Как сделать так, чтобы все сходилось как в бухгалтерии: все объекты созданные при создании формы , при закрытии формы освобождались. Я хочу добиться, чтобы профайлер показывал все мои объекты во вкладке "Garbage objects", т.е. объекты, созданные и тут же уничтоженные.


Я с DataSet/DataTable/Binding дела практически не имел, так что просьба к знающим людям поправить если где неправ.
Возможно, срабатывает описанный выше эффект с неочищенными DataSet, DataTable и т.п.
Т.е. система такая. Твоя таблица висит потому, что на неё есть ссылки от соединения. Далее, в таблице в качестве реакции на события прописаны делегаты методов инстанса формы. А поскольку существует ссылка на метод инстанса объекта в делегате, существует и сам объект-владелец метода.
Re[3]: Помогите разобраться с памятью
От: GarryIV  
Дата: 28.07.09 12:10
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Здравствуйте, Kalina9001, Вы писали:


K>>Возми какой нибудь профайлер(к примеру dotTrace) и посмотри, что там в памяти остается.


ВФ>Скачал, установил профайлер. У профайлера dotTracе есть возможность отслеживать разницу между двумя

ВФ>слепками. Так вот я делаю следующее:

ВФ>1) Mark Memory

ВФ>2) Создаю форму с датасетом и таблицами
ВФ>3) Сразу же закрываю ее
ВФ>4) Get Snapshot

ВФ>И вот что интересно: форма после того, как я ее закрыл все еще жива! По крайней мере она (вместе со всеми БД-объектами) видна во вкладке "Show live objects", а во вкладке "Show dead objects" ее нет.


ВФ>Как сделать так, чтобы все сходилось как в бухгалтерии: все объекты созданные при создании формы , при закрытии формы освобождались. Я хочу добиться, чтобы профайлер показывал все мои объекты во вкладке "Garbage objects", т.е. объекты, созданные и тут же уничтоженные.


http://weblogs.asp.net/fbouma/archive/2009/02/27/winforms-gotcha-form-close-doesn-t-always-call-dispose.aspx

Не твой случай?
WBR, Igor Evgrafov
Re[3]: Помогите разобраться с памятью
От: Kalina9001  
Дата: 28.07.09 12:25
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>И вот что интересно: форма после того, как я ее закрыл все еще жива! По крайней мере она (вместе со всеми БД-объектами) видна во вкладке "Show live objects", а во вкладке "Show dead objects" ее нет.


Вот и смотри в профайлере кто ссылку на эту форму держит.
Dispose у формы вызывается?

Для модальной стоит обернуть вызов в using
using(Form frm = new Form())
{
    frm.ShowDialog();
}


или вызывать Dispose вручную

Form frm = new Form();
try
{
    frm.ShowDialog();
}
finally
{
    frm.Dispose();
    frm = null;
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1231>>
Re[4]: Помогите разобраться с памятью
От: Вульфович Филипп  
Дата: 28.07.09 13:20
Оценка:
Здравствуйте, Kalina9001, Вы писали:

K>Вот и смотри в профайлере кто ссылку на эту форму держит.

K>Dispose у формы вызывается?

А как все это посмотреть?

K>Для модальной стоит обернуть вызов в using


Спасибо, обязательно буду так делать.

Самое интересное, что тот, кто делал мою программу, проектировал формы самым типовым образом, не задумываясь о проблемах памяти,
кидал БД-контролы, привязывал их к полям таблицы (VS при этом сама помещает на форму датасет, адаптеры и bindingsource)

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

Получается построить программу на .Net без утечек памяти очень непросто, без знания "приемов" не обойтись. Но принцип "сборщика мусора" был
сделан, чтобы облегчить программистам жизнь, а не усложнить. Так ведь?
Re[5]: Помогите разобраться с памятью
От: Odi$$ey Россия http://malgarr.blogspot.com/
Дата: 28.07.09 13:40
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Получается построить программу на .Net без утечек памяти очень непросто, без знания "приемов" не обойтись. Но принцип "сборщика мусора" был

ВФ>сделан, чтобы облегчить программистам жизнь, а не усложнить. Так ведь?

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

Не корректно назвать это утечками памяти (в смысле например C++ — сделали new, забыли delete), но от этого хрен редьки действительно слаще не становится.
... << RSDN@Home 1.2.0 alpha 4 rev. 1238>>
Re[5]: Помогите разобраться с памятью
От: fmiracle  
Дата: 28.07.09 13:53
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Получается построить программу на .Net без утечек памяти очень непросто, без знания "приемов" не обойтись.


Ну, осталось отказаться от датасетов и окажется, что и код писать станет легче

ВФ>Но принцип "сборщика мусора" был сделан, чтобы облегчить программистам жизнь, а не усложнить. Так ведь?


Таки упрощает, но испортить можно все что угодно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[5]: Помогите разобраться с памятью
От: Kalina9001  
Дата: 28.07.09 14:00
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

K>>Вот и смотри в профайлере кто ссылку на эту форму держит.

K>>Dispose у формы вызывается?

ВФ>А как все это посмотреть?


Смотри демку тут:
http://www.jetbrains.com/profiler/documentation/demos/dumpMemoryMode.html
... << RSDN@Home 1.2.0 alpha 4 rev. 1231>>
Re[5]: Помогите разобраться с памятью
От: Вульфович Филипп  
Дата: 28.07.09 14:20
Оценка:
Еще одна непонятная вещь.

Я решил немного оптимизировать приложение. У меня три MDI-формы: файлы гостей, счета гостей, пластиковые карты.
Пользователь может открыть одну из них или все три сразу. Также он может нажать на крестик
и убрать их с экрана, а потом опять открыть.

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

Теперь я переписал процедуру открытия формы так:

        private void OpenAccounts()
        {
            if (AccountForm == null) //
            // если это первый раз, то создаем форму и сохраняем ее в переменную
            {
                AccountForm = new Form_Account();
                AccountForm.MdiParent = this;
                AccountForm.WindowState = FormWindowState.Maximized;
            }
            else
            {
                //форма уже создана
            }
            AccountForm.Show();
        }


Я закрываю окно, потом нажимаю кнопку "Счета" (при этом вызывается OpenAccounts). И AccountForm.Show() выполняется с exception: "Cannot access a disposed object."

Когда он успел удалиться? Я ведь сохранил на форму ссылку. Сборщик мусора не мог ведь удалить форму, если на нее остались ссылки?
Как мне тогда сохранить мою "закрытую пользователем" форму от удаления сборщиком мусора?

И подскажите еще пожалуйста (вопрос по dotTrace или по другому профайлеру):

1) Как мне узнать был ли вызван Dispose и когда
2) Как узнать, кто держит объект
Re[6]: Помогите разобраться с памятью
От: anton_t Россия  
Дата: 28.07.09 18:03
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Еще одна непонятная вещь.


ВФ>Я решил немного оптимизировать приложение. У меня три MDI-формы: файлы гостей, счета гостей, пластиковые карты.

ВФ>Пользователь может открыть одну из них или все три сразу. Также он может нажать на крестик
ВФ>и убрать их с экрана, а потом опять открыть.

ВФ>Раньше я при каждом повторном открытии создавал новый экземпляр формы. А тот экземпляр, который пользователь

ВФ>закрывал видимо должен был бы по логике вещей удаляться сборщиком мусора.

ВФ>Теперь я переписал процедуру открытия формы так:


ВФ>
...
ВФ>


ВФ>Я закрываю окно, потом нажимаю кнопку "Счета" (при этом вызывается OpenAccounts). И AccountForm.Show() выполняется с exception: "Cannot access a disposed object."


ВФ>Когда он успел удалиться? Я ведь сохранил на форму ссылку. Сборщик мусора не мог ведь удалить форму, если на нее остались ссылки?

ВФ>Как мне тогда сохранить мою "закрытую пользователем" форму от удаления сборщиком мусора?

Форма не удалена. Кто-то вызвал у неё Dispose. Поставь в методе Dispose бряк и посмотри — кто.
Re[7]: Помогите разобраться с памятью
От: Вульфович Филипп  
Дата: 29.07.09 08:36
Оценка:
Здравствуйте, anton_t, Вы писали:

_>Форма не удалена. Кто-то вызвал у неё Dispose. Поставь в методе Dispose бряк и посмотри — кто.


Поставил. Оказалось Dispose вызывается почти сразу же после закрытия формы (откуда вызывается
в call stack не видно, написано external code).

Как сделать, чтобы форма осталась "жива" после закрытия? Чтобы Dispose не вызывался.
Re[4]: Помогите разобраться с памятью
От: Аноним  
Дата: 29.07.09 10:23
Оценка:
Здравствуйте, GarryIV, Вы писали:

GIV>http://weblogs.asp.net/fbouma/archive/2009/02/27/winforms-gotcha-form-close-doesn-t-always-call-dispose.aspx


GIV>Не твой случай?



Интересненько...
Решил потестить, тем более, что у нас в проекте часто вызывается ShowDialog() без using.

Сделал 2 простые формы:
Главная
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 form2 = new Form2();
            form2.ShowDialog();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }

и диалог
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
    }


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

Возникли вопросы...
В каких случаях возникает утечка?
Для более сложных форм, содержащих контроллы с подкючением в БД?
Или только в случае подписки на событие формы вне класса формы? — Вроде:
           using(Form3 f3 = new Form3())
           {
               this.Resize += new EventHandler(f3.FooEvent);
               f3.ShowDialog(this);
           }


Или не удаляются в таком случае какие-то объекты, используемые самой формой? — типа graphics, drawing и т.д.

Непонятно
Re[5]: Помогите разобраться с памятью
От: Вульфович Филипп  
Дата: 29.07.09 12:12
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Решил потестить, тем более, что у нас в проекте часто вызывается ShowDialog() без using.


Я сделал такое же приложение.

Я решил проследить за выделенной памятью.
И вот что обнаружил:

Допустим я открываю и закрываю форму Form2 — 10 раз.


1) Если я запускаю ее автономно (вне студии и профайлера), то
выделено памяти в начале: 9212 Кб
1а) Открываю и закрываю 10 раз: 10380 Кб.
1б) Нажимаю на кнопку Button2: 10760 Кб

Повторив 1а+1б, получаю: 10960 Кб
Повторив 1а+1б еще несколько раз получаю: 11036 Кб

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

2) Если я запускаю ее под профайлером
памяти в начале: 18516 Кб
2а) Открываю и закрываю 10 раз: 19732 Кб.
2б) Нажимаю на кнопку Button2: 18440 Кб
Т.е. после сборки мусора индикатор памяти падает почти до начального уровня
НО! Повторив п. 2а+2б подряд 10-15 раз, то обнаружил что выделенная памяти все таки растет.
Причем быстрее чем в п.1

В связи с этим возникают вопросы

— Почему память вообще растет? Этот вопрос меня очень волнует, ведь допустим
я буду работать в течении 6-8 часов непрерывно, буду открывать-закрывать формы,
память будет расти все время? Дело в том, я исправил свою первую программу (о которой я писал в начале), добавил вызов Clear у всех таблиц при закрытии формы и память стала рости не в десятки раз, как было в начале, но все
же продолжает расти (т.е. если я открою и закрою форму память увеличивается примерно на 4 Кб и так каждый раз).
Меня интересовал откуда берется этот небольшой прирост, я же теперь все очищаю при закрытии формы.
Но если более простое приложение так же себя ведет (где две почти пустые формы Form1, Form2), надо
разобраться почему происходит прирост памяти в простом случае.

— Почему поведение программы под профайлером и автономная работа различаются?
Re[6]: Помогите разобраться с памятью
От: Аноним  
Дата: 29.07.09 13:56
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

ВФ>Здравствуйте, Аноним, Вы писали:


А>>Решил потестить, тем более, что у нас в проекте часто вызывается ShowDialog() без using.


ВФ>Я сделал такое же приложение.


ВФ>Я решил проследить за выделенной памятью.

ВФ>И вот что обнаружил:

ВФ>Допустим я открываю и закрываю форму Form2 — 10 раз.



ВФ>1) Если я запускаю ее автономно (вне студии и профайлера), то

ВФ>выделено памяти в начале: 9212 Кб
ВФ>1а) Открываю и закрываю 10 раз: 10380 Кб.
ВФ>1б) Нажимаю на кнопку Button2: 10760 Кб

ВФ>Повторив 1а+1б, получаю: 10960 Кб

ВФ>Повторив 1а+1б еще несколько раз получаю: 11036 Кб

ВФ>На таком положении индикатор становится стабильнее. Каждое

ВФ>новое открытие и закрытие формы практически не изменяет значение.
ВФ>Но память все равно растет, правда очень медленно.

ВФ>2) Если я запускаю ее под профайлером

ВФ>памяти в начале: 18516 Кб
ВФ>2а) Открываю и закрываю 10 раз: 19732 Кб.
ВФ>2б) Нажимаю на кнопку Button2: 18440 Кб
ВФ>Т.е. после сборки мусора индикатор памяти падает почти до начального уровня
ВФ>НО! Повторив п. 2а+2б подряд 10-15 раз, то обнаружил что выделенная памяти все таки растет.
ВФ>Причем быстрее чем в п.1

ВФ>В связи с этим возникают вопросы


ВФ>- Почему память вообще растет? Этот вопрос меня очень волнует, ведь допустим

ВФ>я буду работать в течении 6-8 часов непрерывно, буду открывать-закрывать формы,
ВФ>память будет расти все время? Дело в том, я исправил свою первую программу (о которой я писал в начале), добавил вызов Clear у всех таблиц при закрытии формы и память стала рости не в десятки раз, как было в начале, но все
ВФ>же продолжает расти (т.е. если я открою и закрою форму память увеличивается примерно на 4 Кб и так каждый раз).
ВФ>Меня интересовал откуда берется этот небольшой прирост, я же теперь все очищаю при закрытии формы.
ВФ>Но если более простое приложение так же себя ведет (где две почти пустые формы Form1, Form2), надо
ВФ>разобраться почему происходит прирост памяти в простом случае.

ВФ>- Почему поведение программы под профайлером и автономная работа различаются?


Никто не пишет..
Похоже гуру нам не помогут разобраться

Рискну предположить что постоянный небольшой рост памяти связан с тем, что какие-то объекты переходят во второе поколение..
хотя после
GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

они по идее должны удалиться...

в общем хз..
Значит послушаемся/поверим MSDN и будем юзать using для ShowDialog()
Re[6]: Помогите разобраться с памятью
От: Аноним  
Дата: 30.07.09 01:06
Оценка:
Здравствуйте, Вульфович Филипп, Вы писали:

А Вы где смотрите размер памяти? На всякий случай, попробуйте минимизировать и опять развернуть приложение, что будет с памятью?
Re[4]: Помогите разобраться с памятью
От: Аноним  
Дата: 30.07.09 07:39
Оценка:
Здравствуйте, GarryIV, Вы писали:

GIV>http://weblogs.asp.net/fbouma/archive/2009/02/27/winforms-gotcha-form-close-doesn-t-always-call-dispose.aspx


Студийный дизайнер для формы/юзер контрола генерит такую реализацию Dispose():
        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }


Может кто-то объяснит какие ресурсы очищаются при вызове этого Dispose() ?

Спасибо.
Re: Помогите разобраться с памятью
От: Вульфович Филипп  
Дата: 03.08.09 13:14
Оценка:
Оказалось, что проблема решается немного другим путем чем я думал.

Оказалось, что рост памяти, который показывает task manager — это не то же, что
реально выделенная в хипе память.

Если ограничить WorkingSet процесса, тогда task manager не будет показывать постоянный рост памяти.

Вот ссылка на статью:

http://www.gotdotnet.ru/Forums/Common/3634.aspx#3634
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.