Microsoft Windows 7
Рекомендации по улучшению стабильности приложений
Создание стабильных приложений является нетривиальной задачей, требующей не только понимания нюансов работы системы, но и выполнения ряда правил. Стабильные приложения, надежно работающие под управлением операционной системы, не только повышают производительность пользователей, но и делают саму систему более надежной, производительной и безопасной.
В настоящем цикле статей мы приведем ряд рекомендаций по улучшению стабильности приложений. Мы познакомимся с техникой, позволяющей избежать утечек памяти и предотвратить зависание приложений, а также обсудим использование механизма Application Restart and Recovery, обеспечивающего перезапуск приложений, которые или заблокировали какие-либо ресурсы, или перестали реагировать на сообщения системы и механизма Windows Error Reporting, позволяющего собирать данные о сбоях, происходящих в приложениях.
Утечки памяти — это класс ошибок в коде приложений, в результате которых приложение не освобождает ранее занятую память. Из-за утечек памяти может снижаться производительность как приложения, так и самой операционной системы — блокирование больших фрагментов памяти приводит к более интенсивному использованию механизма постраничной виртуализации памяти на жестком диске, что является более медленной операцией по сравнению с работой непосредственно с оперативной памятью. По завершении работы приложения Windows освобождает всю занятую процессом память, так что приложения, которые выполняются за короткое время, не могут заметно повлиять на производительность системы. Проблемы возникают с процессами и приложениями, которые выполняются длительное время, например такими, как расширения для Windows Explorer, — утечки памяти в этом случае могут привести к существенному снижению производительности системы и, как следствие, к необходимости перезагрузки системы для восстановления ее нормальной работоспособности или перезагрузки самих приложений, если они поддерживают такую возможность, но об этом далее.
Существует несколько способов выделения блоков памяти в приложении. Каждый способ может привести к утечке памяти в том случае, если ранее выделенная память не будет своевременно освобождена. Приведем несколько примеров корректного использования функций выделения памяти:
- выделение области «кучи» (heap) через функцию HeapAlloc() или ее эквиваленты для библиотеки языка С++ malloc или new. Для освобождения памяти следует применять «парные» функции HeapFree(), free() и delete(). Отметим, что начиная с Windows Vista автоматически поддерживается так называемая низкофрагментированная «куча», использование которой позволяет снизить фрагментацию «кучи» — состояние, при котором в «куче» достаточно памяти для удовлетворения запроса на выделение памяти, но нет последовательной области необходимой длины;
- прямое выделение памяти через функцию VirtualAlloc(). Для освобождения памяти, выделенной таким способом, следует применять функцию VirtualFree();
- использование ссылок (handle), полученных через функции CreateFile(), CreateEvent(), CreateThread(). Освобождение памяти осуществляется с помощью функции CloseHandle(), которой в качестве одного из параметров передается полученная одной из перечисленных ранее функций Create…() ссылка;
- применение ссылок, полученных через соответствующие функции подсистем USER и GDI. По умолчанию каждому процессу выделяется квота на 10 тыс. ссылок. Для каждой конкретной функции существует «парная» функция, освобождающая занятую память, — подробнее см. в документации к Windows SDK.
Для обнаружения утечек памяти нужно следить за поведением приложения с течением времени. Это можно делать с помощью Windows Task Manager — следует добавить к списку отображаемых колонок колонки Memory-Commit Size, Handles, User Objects и GDI Objects. Это позволит вам определить точку отсчета в потреблении ресурсов вашим приложением (рис. 1).
Рис. 1. Task Manager
Для более детального изучения работы приложения и упрощения определения проблем с утечкой памяти необходимо использовать специализированные средства, предоставляемые компанией Microsoft. К ним относятся:
- средства мониторинга, входящие в состав Windows 7, — Performance Monitor и Resource Monitor (рис. 2 и 3);
Рис. 2. Performance Monitor
Рис. 3. Resource Monitor
- средство тестирования приложений Application Verifier (рис. 4);
Рис. 4. Application Verifier
- для анализа выделения областей памяти в «куче» следует применять утилиту UMDH, входящую в состав Debugging Tools for Windows;
- утилита XPerf также позволяет трассировать выделение памяти в «куче».
Для того чтобы ваши приложения корректно работали с ресурсами системы, в первую очередь с памятью, следует придерживаться следующих правил:
в коде на С++ используйте smart pointers — шаблонные классы, имитирующие обычные указатели с возможностью очистки, освобождения памяти, проверки границ и тому подобного для выделения памяти как в «куче», так и для получения ресурсов Win32, включая ссылки. Библиотека C++ Standard Library содержит класс auto_ptr (описан в <memory>), который можно использовать для выделения памяти в «куче». В состав библиотеки ATL входит большое число классов для автоматического управления ресурсами на уровне как объектов «кучи», так и ресурсов Win32;
- применяйте встроенные функции компилятора, например _com_ptr_t для преобразования указателей на COM-интерфейсы в smart pointers и упрощения подсчета ссылок (reference). Для других COM-типов также существуют схожие классы — например _bstr_t и _variant_yt;
- следите за применением памяти в коде на .NET. Обратите внимание на то, что код на .NET также подвержен утечкам памяти — это происходит из-за того, что сборщик мусора (garbage collector) не освобождает память до тех пор, пока существуют ссылки на нее;
- в веб-приложениях утечки памяти могут возникать из-за циклических ссылок между COM-объектами и кодом на JScript. Internet Explorer 8, входящий в состав Windows 7, позволяет решить большинство проблем, связанных с такими утечками памяти. Для предыдущих версий браузера следует использовать специальное отладочное средство — JavaScript Memory Leak Detector;
- старайтесь не применять несколько вариантов завершения работы функции. Все выделения памяти, присвоенные переменным в области действия функции, должны быть освобождены перед ее завершением, желательно в одном блоке кода, доступном всегда независимо от поведения функции;
- не используйте исключения без предварительного освобождения памяти, занятой всеми локальными переменными в области действия функции. В случае применения стандартных исключений предусмотрите освобождение памяти в блоке __finally. Если используются исключения С++, все выделения памяти в «куче» и получение ссылок должны производиться через smart pointers;
- не забывайте вызывать функцию PropVariantClear() перед удалением или повторной инициализацией объекта PROPVARIANT.
Итак, мы рассмотрели основные причины появления утечек памяти, а также привели рекомендации по корректному выделению памяти и ресурсов Windows, включая рекомендации для кода на С/С++, управляемого кода и клиентского кода веб-приложений.
***
Следующая тема, которая имеет непосредственное отношение к стабильности как приложений, так и самой системы, — это зависание приложений. Ее мы рассмотрим в следующей статье данного цикла.