С аналогичными трудностями сталкиваются и разработчики приложений на основе собственных кодов, однако они должны управлять памятью самостоятельно. Полная сборка мусора обходится недешево; чтобы восстановить память, занимаемую ненужными объектами, среда времени выполнения должна просмотреть все объекты высшего уровня, спускаясь вниз по деревьям подобъектов для отыскания всех объектов, которые по-прежнему используются приложением. Выполнение каждой операции по очистке памяти от "мусора" требует некоторых минимальных накладных расходов, объем которых зависит от количества объектов, которые необходимо просмотреть; многократное выполнение этой операции во многих случаях приводит к ощутимому замедлению работы приложений.
Частота выполнения сборки мусора напрямую зависит от того, насколько быстро ваш код расходует память приложения. Дефицит памяти в приложениях может быть обусловлен двумя факторами: 1) суммарным объемом поддерживаемого состояния приложения, включая всевозможные формы, глобальные объекты, пользовательские данные и любые другие объекты, содержащиеся внутри глобальных родительских объектов, и 2) временными объектами, которые создаются и разрушаются в процессе выполнения ваших алгоритмов. Если посмотреть на график изменения объема памяти, расходуемой приложением в процессе своего выполнения, то он будет иметь пилообразную форму. Высота зубьев этой пилы определяется тем, какой объем памяти вы оставляете для использования вашими алгоритмами, где имеется в виду память, не входящая в состав постоянно распределенной памяти приложения. Наклон же зубьев зависит от того, насколько эффективны процедуры распределения памяти для объектов, используемые в ваших алгоритмах. Чем более экономичны алгоритмы в отношении размещения новых объектов, тем более пологий наклон имеют зубья. В конечном счете, объем занимаемой приложением памяти начинает превышать некоторое пороговое значение, что приводит к запуску механизма сборки мусора, осуществляющего освобождение памяти от неиспользуемых объектов.
На рис. 12.1, 12.2, 12.3 и 12.4 проиллюстрированы четыре основных типа потребления памяти.
Рис. 12.1 схематически иллюстрирует поведение приложения в отношении потребления памяти в соответствии с наихудшим сценарием, который характеризуется высоким объемом постоянно распределенной памяти и неэкономным расходованием памяти в процессе работы алгоритмов. В этом случае постоянно распределенная память мобильного приложения занимает почти всю память, имеющуюся в системе. В результате этого в случае непрерывного размещения и уничтожения объектов, осуществляемого неэффективными алгоритмами, в памяти остается совсем мало места. Очень важно не забывать о том, что область памяти, занимаемая объектом, не может быть повторно использована сразу же после уничтожения ссылки на объект; чтобы восстановить память с целью сделать ее доступной для дальнейшего использования, должна быть выполнена операция сборки мусора. Непрерывное размещение и удаление объектов является причиной увеличения крутизны наклона зубьев пилообразного графика потребления памяти. Нехватка места для размещения объектов в памяти делает необходимой ее частую очистку от "мусора", что каждый раз сопровождается дополнительными накладными расходами.
В результате этого производительность резко ухудшается, поскольку сборщик мусора вынужден выполняться практически непрерывно, чтобы приложение могло хоть как-то работать.
Рис. 12.1. Наихудший случай: чрезмерное потребление памяти состоянием приложения, неэкономичные алгоритмы
На рис. 12.2 представлен промежуточный в отношении производительности случай. Хотя этому варианту поведения системы, как и предыдущему, также свойственно неэкономное размещение объектов в памяти алгоритмами приложения, тем не менее, объем постоянно распределенной памяти в этом случае гораздо меньше. Это означает, что, несмотря на то, что наклон линии, отображающей процессы непрерывного размещения и уничтожения объектов, остается крутым, имеется гораздо больше свободного пространства, способного дольше хранить "мусор". Таким образом, необходимость в сборке мусора будет возникать гораздо реже, в результате чего производительность значительно улучшается по сравнению с предыдущим случаем.
Рис. 12.3 иллюстрирует еще одну промежуточную модель управления памятью, обратную той, которая рассматривалась выше. Кривая потребления памяти характеризуется высоким уровнем постоянно распределенной памяти, соответствующей глобальным объектам и системным данным, но рабочая память используется алгоритмами приложения гораздо более эффективным образом. Хотя ее объем и невелик, однако она экономнее расходуется для размещения объектов алгоритмами, что устраняет необходимость в ее непрерывном восстановлении посредством механизма сборки мусора.