Пришел на работу программистом. В наследство досталось программа, написанная другим человеком на C#.
Программа — база данных на MS SQL 2000 cервере, уже написано много логики, переписать все с нуля нереально долго. Состоит из кучи форм, на которых поставлены датасеты, датаадаптеры и bindingsource. Каждая форма сама открывает connection, лезет в базу данных и загружает все данные (через методы адаптеров Fill) после окончания сохраняет данные (через методы адаптеров Update). Работает медленно, но хуже всего, что теряется память. Если раз 10-20 открыть и закрыть форму — диспетчер задач показывает что память растет. Иногда конечно происходит небольшой скачок вниз (может быть работает сборщик мусора), но в среднем память все равно увеличивается.
Поскольку это C# я никогда не освобождаю объекты, но я новичок в .Net, до этого работал много на Delphi. В Delphi освобождать объекты необходимо, но в C# я всегда думал, что с этим справится сборщик мусора. Может я жестоко ошибаюсь? Объем памяти при открытии программы 32 Мб, через час может достигать 200 Мб и больше. Пользователи жалуются что через несколько часов компьютер намертво подвисает и приходится перезагружать.
Пробовал в нескольких местах вставлять GC.Collect но это не помогает. Помогите, что делать?
Может быть надо везде освобождать объекты явно при закрытии формы? Или есть какой-то другой способ бороться с утечкой памяти.
ВФ>Пробовал в нескольких местах вставлять GC.Collect но это не помогает. Помогите, что делать? ВФ>Может быть надо везде освобождать объекты явно при закрытии формы? Или есть какой-то другой способ бороться с утечкой памяти.
Такая проблема у меня была. Помог инет . Надо делать вызов метода Clear() у всех объектов DataTable и DataSet после того как они тебе не нужны (при закрытии формы, например).
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>Поскольку это C# я никогда не освобождаю объекты, но я новичок в .Net, до этого работал много на Delphi. В Delphi освобождать объекты необходимо, но в C# я всегда думал, что с этим справится сборщик мусора.
Он справится, если на объект нет никаких ссылок. Судя по всему, остаются ссылки на "мертвые" объекты.
Это могут быть как явные ссылки, так и неявные — например, в виде объектов, на события от которых кто-то подписан (например, основная ворма или само приложение). Во втором случае найти их не так-то просто.
Сборщик мусора при этом считает их "нужными" объектами и не удаляет.
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>Поскольку это C# я никогда не освобождаю объекты, но я новичок в .Net, до этого работал много на Delphi. В Delphi освобождать объекты необходимо, но в C# я всегда думал, что с этим справится сборщик мусора. Может я жестоко ошибаюсь? Объем памяти при открытии программы 32 Мб, через час может достигать 200 Мб и больше. Пользователи жалуются что через несколько часов компьютер намертво подвисает и приходится перезагружать.
Наверно идиотский вопрос, но все же — компиляция описываемой программы ТОЧНО проведена в релиз-варианте? Не дебаг?
Здравствуйте, Kalina9001, Вы писали:
K>Возми какой нибудь профайлер(к примеру dotTrace) и посмотри, что там в памяти остается.
Скачал, установил профайлер. У профайлера dotTracе есть возможность отслеживать разницу между двумя
слепками. Так вот я делаю следующее:
1) Mark Memory
2) Создаю форму с датасетом и таблицами
3) Сразу же закрываю ее
4) Get Snapshot
И вот что интересно: форма после того, как я ее закрыл все еще жива! По крайней мере она (вместе со всеми БД-объектами) видна во вкладке "Show live objects", а во вкладке "Show dead objects" ее нет.
Как сделать так, чтобы все сходилось как в бухгалтерии: все объекты созданные при создании формы , при закрытии формы освобождались. Я хочу добиться, чтобы профайлер показывал все мои объекты во вкладке "Garbage objects", т.е. объекты, созданные и тут же уничтоженные.
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>Здравствуйте, 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 и т.п.
Т.е. система такая. Твоя таблица висит потому, что на неё есть ссылки от соединения. Далее, в таблице в качестве реакции на события прописаны делегаты методов инстанса формы. А поскольку существует ссылка на метод инстанса объекта в делегате, существует и сам объект-владелец метода.
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>Здравствуйте, Kalina9001, Вы писали:
K>>Возми какой нибудь профайлер(к примеру dotTrace) и посмотри, что там в памяти остается.
ВФ>Скачал, установил профайлер. У профайлера dotTracе есть возможность отслеживать разницу между двумя ВФ>слепками. Так вот я делаю следующее:
ВФ>1) Mark Memory ВФ>2) Создаю форму с датасетом и таблицами ВФ>3) Сразу же закрываю ее ВФ>4) Get Snapshot
ВФ>И вот что интересно: форма после того, как я ее закрыл все еще жива! По крайней мере она (вместе со всеми БД-объектами) видна во вкладке "Show live objects", а во вкладке "Show dead objects" ее нет.
ВФ>Как сделать так, чтобы все сходилось как в бухгалтерии: все объекты созданные при создании формы , при закрытии формы освобождались. Я хочу добиться, чтобы профайлер показывал все мои объекты во вкладке "Garbage objects", т.е. объекты, созданные и тут же уничтоженные.
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>И вот что интересно: форма после того, как я ее закрыл все еще жива! По крайней мере она (вместе со всеми БД-объектами) видна во вкладке "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;
}
Здравствуйте, Kalina9001, Вы писали:
K>Вот и смотри в профайлере кто ссылку на эту форму держит. K>Dispose у формы вызывается?
А как все это посмотреть?
K>Для модальной стоит обернуть вызов в using
Спасибо, обязательно буду так делать.
Самое интересное, что тот, кто делал мою программу, проектировал формы самым типовым образом, не задумываясь о проблемах памяти,
кидал БД-контролы, привязывал их к полям таблицы (VS при этом сама помещает на форму датасет, адаптеры и bindingsource)
Я бы сделал почти так же, будучи новичком и зная, что в .Net "с памятью все решено".
Но оказалось, что получается нужно знать очень много довольно нетривиальных вещей, про ссылки,
которые мешают быть объекту освобожденным, про то, что датасет нормально освободиться не может, если не вызвать
Clear у всех таблиц.
Получается построить программу на .Net без утечек памяти очень непросто, без знания "приемов" не обойтись. Но принцип "сборщика мусора" был
сделан, чтобы облегчить программистам жизнь, а не усложнить. Так ведь?
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>Получается построить программу на .Net без утечек памяти очень непросто, без знания "приемов" не обойтись. Но принцип "сборщика мусора" был ВФ>сделан, чтобы облегчить программистам жизнь, а не усложнить. Так ведь?
с памятью-то GC худо-бедно справляется, но кроме памяти любая реальная программа утыкана неуправляемыми ресурсами — файлы, БД, таймеры и т.д. — их все также надо корректно отслеживать и ручками освобождать, иначе за них цепляются другие объекты и ты видишь неосвобождающуюся память.
Не корректно назвать это утечками памяти (в смысле например C++ — сделали new, забыли delete), но от этого хрен редьки действительно слаще не становится.
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>Получается построить программу на .Net без утечек памяти очень непросто, без знания "приемов" не обойтись.
Ну, осталось отказаться от датасетов и окажется, что и код писать станет легче
ВФ>Но принцип "сборщика мусора" был сделан, чтобы облегчить программистам жизнь, а не усложнить. Так ведь?
Здравствуйте, Вульфович Филипп, Вы писали:
K>>Вот и смотри в профайлере кто ссылку на эту форму держит. K>>Dispose у формы вызывается?
ВФ>А как все это посмотреть?
Я решил немного оптимизировать приложение. У меня три 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) Как узнать, кто держит объект
Здравствуйте, Вульфович Филипп, Вы писали:
ВФ>Еще одна непонятная вещь.
ВФ>Я решил немного оптимизировать приложение. У меня три MDI-формы: файлы гостей, счета гостей, пластиковые карты. ВФ>Пользователь может открыть одну из них или все три сразу. Также он может нажать на крестик ВФ>и убрать их с экрана, а потом опять открыть.
ВФ>Раньше я при каждом повторном открытии создавал новый экземпляр формы. А тот экземпляр, который пользователь ВФ>закрывал видимо должен был бы по логике вещей удаляться сборщиком мусора.
ВФ>Теперь я переписал процедуру открытия формы так:
ВФ>
...
ВФ>
ВФ>Я закрываю окно, потом нажимаю кнопку "Счета" (при этом вызывается OpenAccounts). И AccountForm.Show() выполняется с exception: "Cannot access a disposed object."
ВФ>Когда он успел удалиться? Я ведь сохранил на форму ссылку. Сборщик мусора не мог ведь удалить форму, если на нее остались ссылки? ВФ>Как мне тогда сохранить мою "закрытую пользователем" форму от удаления сборщиком мусора?
Форма не удалена. Кто-то вызвал у неё Dispose. Поставь в методе Dispose бряк и посмотри — кто.
Интересненько...
Решил потестить, тем более, что у нас в проекте часто вызывается 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 и т.д.
Здравствуйте, Аноним, Вы писали:
А>Решил потестить, тем более, что у нас в проекте часто вызывается 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();