Управление памятью в XNU - Введение в VM (Часть 1)
Введение в управление памятью Link to heading
Операционная система (ОС) – это комплекс программ в основе которых лежит управление памятью. От эффективности этого управления напрямую зависит производительность и стабильность работы как системы, так и каждого запущенного приложения. Любая операция, выполняемая в операционной системе сводится к управлению памятью. Графический интерфейс, файлы, сокеты и другие сложные объекты – это всего лишь абстракции, построенные на основе механизмов управления памятью. Поэтому управление памятью является одной из ключевых задач операционной системы.
В этой серии статей мы рассмотрим, как ядро операционной системы, в частности ядро XNU, осуществляет управление памятью.
Уровни управления памятью Link to heading
Управление памятью осуществляется на двух уровнях. На уровне ядра память выделяется путем запроса физических страниц с использованием блока управления памятью (MMU - Memory Management Unit). Однако, использование физических страниц напрямую не всегда эффективно. Поэтому в пользовательском режиме применяются дополнительные абстракции, такие как куча (Heap) и другие промежуточные API. Пользовательский режим предоставляет приложениям возможность динамического выделения памяти, используя функции *alloc() и free() в C, или аналогичные механизмы в других языках программирования. Эти функции, в свою очередь, взаимодействуют с системой управления памятью, предоставляемой ядром, для выделения и освобождения блоков памяти. Различные алгоритмы управления кучей оптимизируют процесс выделения и освобождения памяти, стремясь минимизировать фрагментацию и повысить производительность.
Что такое стек, куча и MMU? Link to heading
- Стек (Stack) - область памяти, в которой хранятся временные данные: локальные переменные, параметры функций и адрес возврата. Стек работает по принципу LIFO (последний зашёл - первый вышел) и выделяется автоматически. Он очень быстрый, но ограничен по размеру.
- Куча (Heap) - область памяти, из которой программа динамически запрашивает блоки произвольного размера во время выполнения. Выделение и освобождение памяти в куче осуществляется вручную или через сборщик мусора (в зависимости от языка).
- MMU (Memory Management Unit) - аппаратный блок в процессоре, отвечающий за трансляцию виртуальных адресов в физические, а также за защиту памяти. MMU играет ключевую роль в изоляции процессов и реализации виртуальной памяти.
Организация памяти: страницы и MMU Link to heading
Современные процессоры работают с памятью, разбивая её на страницы – атомарные чанки в оперативной памяти. Стандартный размер страницы обычно составляет 4 КБ (в архитектурах Intel и ARMv7), но может варьироваться: в ARMv8 используются страницы размером до 16 КБ, а Intel даже поддерживает “huge pages” размером до 2 МБ. Размер страницы критически важен для эффективного управления памятью. Он определяет минимальный объем памяти, к которому процессор может обратиться. Даже для доступа к отдельному байту данных необходимо загрузить всю страницу, в которой находится этот байт.
При обращении к памяти процессор использует MMU, который отвечает за преобразование виртуальных адресов памяти в физические. Этот процесс трансляции виртуальной памяти в физическую в значительной степени прозрачен - каждый процесс работает с виртуальными адресами и не имеют прямого доступа к физической оперативной памяти. Только ядро обладает возможностью изменять таблицы страниц, которые используются совместно с MMU и определяют соответствие между физическими страницами и виртуальными адресами. Таблицы страниц – это ключевой элемент виртуальной памяти, позволяющий изолировать процессы друг от друга и обеспечивать защиту памяти. Ядро управляет этими таблицами, переключаясь между различными таблицами страниц для каждого процесса, что позволяет каждому процессу иметь собственное виртуальное адресное пространство.
Техники эффективного использования памяти Link to heading
Процессы могут потреблять больше памяти, чем физически установлено в системе. Поэтому применяются различные подходы, для максимально эффективного использования доступной памяти.
Копирование при записи (Copy-on-Write, CoW) Link to heading
это распространенная техника, позволяющая эффективно использовать память. Она заключается в том, что несколько процессов используют одну физическую копию данных. Вместо создания отдельных копий данных, процессы совместно используют одну физическую копию. Это позволяет избежать излишнего потребления памяти, поскольку процессы работают с памятью так, будто она отображена исключительно для них, даже при совместном использовании данных. Метод также известен как implicit sharing. Пока содержимое памяти только читается, никаких дополнительных действий не требуется – все операции чтения получают одни и те же данные. Однако, как только один из процессов попытается изменить (записать) такую страницу, срабатывает механизм копирования при записи. MMU генерирует прерывание page fault, которое перехватывается ядром. Обнаружив, что ошибка исправима, ядро создает отдельную физическую копию страницы для модифицирующего процесса. CoW широко используется в современных операционных системах для повышения производительности и эффективности управления памятью. Однако существуют и альтернативные подходы. Например, операционная система Linux использует технику Kernel Samepage Merging (KSM). KSM обнаруживает идентичные страницы в памяти и автоматически удаляет дубликаты, оставляя только одну физическую копию страницы. KSM дополняет CoW, позволяя находить и объединять одинаковые страницы между процессами, что делает использование памяти ещё более эффективным, особенно в условиях виртуализации. На момент написания статьи, точных данных о том поддерживает ли XNU функциональность KSM нет.
Подкачка (Paging) Link to heading
Это техника управления памятью, при которой неиспользуемые физические страницы в памяти перемещаются во вторичное хранилище, например, на жесткий диск. Для файлов, отображенных в память (memory-mapped files), это означает запись изменений обратно в исходный файл. А для анонимной памяти, выделенной через malloc, подкачка подразумевает запись данных в область подкачки (Swap), что обычно называют своппингом памяти.
Типы состояний страниц:
Приблизительно, с физической точки зрения, страница может находиться в одном из нескольких состояний жизненного цикла, как показано на рисунке ниже:

| Состояние | Описание |
|---|---|
| Free | Означает, что физическая страница не используется. Она может быть немедленно освобождена, если возникнет необходимость. |
| Active | Означает, что физическая страница в данный момент используется и недавно была запрошена. Вероятность её вытеснения мала, если только не закончатся неактивные страницы. Если страница не будет запрошена в ближайшее время, она станет неактивной. |
| Inactive | Физические страницы, которые использовались ранее, но стали неактивными после истечения времени ожидания. Такие страницы с большей вероятностью будут вытеснены, если возникнет необходимость. Однако если неактивная страница будет запрошена, она снова станет активной. |
| Speculative | Загружаются и оцениваются в преддверии их возможного использования, которое может быть как срочным, так и нет. Такие страницы могут быть либо востребованы (и переведены в активное состояние), либо нет (в этом случае они станут неактивными по истечении времени ожидания). В случае нехватки памяти спекулятивные страницы могут быть немедленно освобождены (поскольку они ещё не были использованы). |
| Wired | Страницы памяти являются резидентными и, независимо от их статуса (активные или неактивные), остаются в памяти до явного “освобождения” (unwired). |
| Swap | Страницы, выгруженные из ОЗУ на диск и находящиеся в swap-файле. |
| File | Страницы, связанные с memory-mapped файлами. Их изменения могут быть синхронизированы с файловой системой. |
Ядро поддерживает page throttling. Это искусственная задержка, которая накладывается на процессы, требующие подкачки страниц или создания новых страниц. Это предназначено для предотвращения ситуаций, когда некоторые процессы могут потреблять слишком много памяти, что в итоге снижает общую производительность системы.
Анализ памяти с помощью vm_stat Link to heading
Утилита vm_stat в OS X предоставляет подробную статистику по виртуальной памяти в системе. Эта информация полезна для диагностики проблем с производительностью, понимания использования памяти и оптимизации системы. Вывод vm_stat разделен на несколько категорий, каждая из которых отражает определенный аспект управления памятью. Важно понимать, что значения этих показателей динамически меняются в зависимости от нагрузки на систему.
Ключевые показатели Link to heading
| Показатель | Описание |
|---|---|
| Pages free | Количество страниц памяти, которые в данный момент не используются и доступны для выделения процессам. Большое количество свободных страниц обычно указывает на достаточное количество оперативной памяти. |
| Pages active | Количество страниц памяти, которые в данный момент используются процессами. |
| Pages inactive | Количество страниц памяти, которые не используются в данный момент, но могут быть использованы в будущем. Это страницы, которые были недавно освобождены, но еще не были удалены из кэша. |
| Pages speculative | Страницы, которые были выделены заранее, на основе предположения о том, что они будут нужны в ближайшем будущем. Это механизм, используемый для ускорения выделения памяти, но может привести к неэффективному использованию памяти, если предположение окажется неверным. |
| Pages throttled | Страницы, доступ к которым был ограничен из-за нехватки ресурсов. Throttling - механизм, используемый для предотвращения полного отказа системы при нехватке памяти. Отсутствие throttling указывает на то, что система не испытывает серьезной нехватки памяти. |
| Pages wired down | Страницы, которые не могут быть выгружены на диск, даже если система испытывает нехватку памяти. Это страницы, которые содержат критически важные данные, необходимые для работы системы. Выгрузка таких страниц может привести к сбоям в работе. |
| Pages purgable | Страницы, которые могут быть выгружены на диск для освобождения памяти. Это страницы, которые не являются критически важными для работы системы. |
| Translation faults | Количество ошибок перевода виртуальных адресов в физические. Высокое количество ошибок перевода может указывать на проблемы с драйверами устройств или с самим процессором. |
| Pages copy-on-write | Страницы, которые были выделены с использованием механизма copy-on-write. Этот механизм используется для обеспечения безопасности и разделения памяти между процессами. |
| Pages zero filled | Страницы, которые были выделены и заполнены нулями. Обычно используются для выделения памяти под объекты, которые не содержат данных. |
| Pages reactivated | Страницы, которые были выгружены на диск, а затем повторно загружены в память. Это может указывать на проблемы с управлением памятью или на неэффективное использование памяти. |
| Pages purged | Страницы, которые были удалены из памяти для освобождения места. Это может быть сделано системой для управления памятью или процессами. |
| File-backed pages | Страницы памяти, которые хранятся на диске в файлах. Это может быть полезно для хранения данных, которые не часто используются. |
| Anonymous pages | Страницы памяти, которые не связаны с файлами. Обычно используются для выделения памяти процессам. |
| Pages stored in compressor | Страницы памяти, которые были сжаты для экономии места. Это может быть полезно для уменьшения потребления памяти, но может замедлить работу системы. |
| Pages occupied by compressor | Страницы памяти, которые занимают место в компрессоре. |
| Decompressions | Количество операций декомпрессии, выполненных компрессором. |
| Compressions | Количество операций сжатия, выполненных компрессором. |
| Pageins | Количество операций ввода страниц (страницы были загружены с диска в память). |
| Pageouts | Количество операций вывода страниц (страницы были выгружены из памяти на диск). |
| Swapins | Количество операций свопа (перемещение страниц между оперативной памятью и диском). |
| Swapouts | Количество операций вывода на диск (перемещение страниц с оперативной памяти на диск). |
Диагностика проблем с памятью Link to heading
1. Высокие значения Pageins/Pageouts - активный своппинг Link to heading
Критические уровни:
- Больше 1000 операций в секунду - тревожный сигнал
- Соотношение 1:3 (Pageins:Pageouts) указывает на хроническую нехватку памяти
Последствия:
- Лавинообразное снижение производительности (до 100x)
- Ускоренный износ SSD (особенно критично для iOS-устройств)
- Увеличение энергопотребления
2. Высокие Swapins/Swapouts - нехватка RAM Link to heading
Особенности:
- На iOS появляется только на устройствах с A12 Bionic и новее
- На macOS - классический механизм подкачки
Пороговые значения:
| Устройство | Критический уровень (операций/сек) |
|---|---|
| iPhone (A12+) | >500 |
| Mac с HDD | >300 |
| Mac с SSD | >1000 |
Рекомендации для разработчиков Link to heading
1. Управление памятью в Swift Link to heading
- Аккуратно используйте
structвместоclassгде возможно (размещение на стеке быстрее, чем в куче, но стек имеет свойство заканчиваться). - Избегайте retain cycles:
weak– для опциональных ссылок,unowned– когда объект гарантированно существует.
autoreleasepoolдля циклов с большим числом временных объектов (особенно при работе с Objective-C API).
2. Оптимизация коллекций Link to heading
- Выбирайте правильные типы:
Array– быстрый индексированный доступ,Set– быстрая проверка наличия элемента,Dictionary– быстрый доступ по ключу.
- Используйте
ContiguousArrayдля повышения производительности при работе с примитивами (Int,Doubleи т. д.). - Ленивые вычисления:
lazy.map,lazy.filterдля отложенной обработки больших коллекций.
3. Оптимизация строк (String)
Link to heading
- Swift
String– копирование при записи (Copy-on-Write), но большие строки могут нагружать память. - Используйте
Substringдля временных представлений (не создает новую копию).
4. Оптимизация изображений и данных Link to heading
- Загружайте изображения в оптимальном размере (не храните
UIImageв полном разрешении, если оно отображается в маленькомUIImageView). - Используйте
NSCacheдля кэширования тяжелых объектов (изображений, данных). - Освобождайте ресурсы вручную (например,
UIImageпосле использования).
5. Инструменты для анализа памяти Link to heading
- Instruments → Allocations – отслеживание утечек и потребления памяти.
- Instruments → Leaks – поиск retain cycles.
- Debug Memory Graph (Xcode) – визуализация объектов в памяти.
6. Многопоточность и память Link to heading
- Используйте
DispatchQueueи акторы (Actor) для thread-safe доступа. - Избегайте гонки данных – синхронизация через
@MainActor,@globalActor.
7. Оптимизация ARC (Automatic Reference Counting) Link to heading
- Используйте non-escaping замыкания, по умолчанию.
- Для @escaping замыканий используйте [weak self] или [unowned self], чтобы избежать retain cycles.
- Избегайте ненужных захватов (capture lists) в non-escaping замыканиях - они и так не создают проблем с памятью.
- Локальные объекты: Не беспокойтесь о них - они освобождаются автоматически.
Вывод Link to heading
Оптимизация памяти в Swift сводится к:
- Правильному выбору типов (
structvsclass), - Избеганию retain cycles,
- Использованию ленивых вычислений (
lazy), - Работе с инструментами профилирования (Instruments, Debug Memory Graph).
Особенности iOS Link to heading
На iOS ситуация осложняется:
- Swap доступен только на A12+
- Жесткие ограничения на фоновую активность
- Нет ручного управления параметрами
3. Много Translation faults - проблемы с драйверами/железом Link to heading
Что такое Translation Faults? Link to heading
Translation Faults (ошибки трансляции адресов) возникают, когда процессор не может преобразовать виртуальный адрес в физический. Translation fault - это нормальное событие при первом доступе к странице или при pagein. Это может происходить по нескольким причинам:
Valid Faults (нормальные ситуации):
- Первое обращение к странице
- Доступ к странице, выгруженной на диск (pagein)
- Copy-on-Write операции
Invalid Faults (проблемные):
- Обращение к невыделенной памяти
- Нарушения прав доступа
- Аппаратные сбои MMU
Нормальные и критические значения Link to heading
Базовые уровни для разных систем:
| Система | Нормальный уровень | Тревожный уровень | Критический уровень |
|---|---|---|---|
| macOS (десктоп) | <500/сек | 500-2000/сек | >2000/сек |
| macOS (сервер) | <1000/сек | 1000-5000/сек | >5000/сек |
| iOS (фоновый режим) | <100/сек | 100-500/сек | >500/сек |
| iOS (активное приложение) | <300/сек | 300-1000/сек | >1000/сек |
Подробная диагностика в OS X Link to heading
Для подробной диагностики Translation faults в macOS можно использовать следующие утилиты
# Показать все сторонние драйверы с детализацией
kextstat -l | grep -v com.apple
# Проверить подпись драйверов
kmutil showloaded --list-only --bundle-identifier | grep -v com.apple
# Поиск проблемных драйверов123
sudo kmutil log show --filter "error" | grep "translation fault"
Интерпретация результатов:
- Драйверы без подписи Apple могут быть причиной проблем
- Особое внимание к драйверам графических карт и сетевых интерфейсов
Дополнительные рекомендации:
- Используйте kextunload для временного отключения подозрительных драйверов и проверки стабильности системы.
- В Safe Mode macOS загружает только базовые драйверы Apple - это помогает локализовать проблему.
- Для глубокого анализа логов используйте:
log show --predicate 'senderImagePath contains "kext"' --last 24h
4. Высокие Pages throttled - экстремальная нехватка памяти Link to heading
Описание:
Показатель Pages throttled отражает, что часть(и) оперативной памяти временно заблокирована(ы) или замедлена(ы) из-за нехватки ресурсов. Высокие значения (>5%) сигнализируют о серьезной нагрузке, а при превышении 20% система может работать крайне нестабильно: приложения будут закрываться, интерфейс - зависать, а фоновые задачи - терминироваться.
Причины:
- Утечки памяти в приложениях (особенно браузеры, IDE, графические редакторы).
- Одновременная работа множества ресурсоемких программ.
- Недостаточный объем ОЗУ для текущих задач.
- Агрессивное кэширование данных.
Уровни серьезности:
| % throttled | Состояние системы | Рекомендуемые действия |
|---|---|---|
| <5% | Нормальная нагрузка | Мониторинг, оптимизация фоновых процессов |
| 5-20% | Предупреждение | Закрыть ненужные приложения, проверить на утечки |
| >20% | Критическое состояние | Немедленное освобождение памяти, перезагрузка |
Экстренные меры:
Для macOS:
# Принудительная очистка неактивной памяти sudo purge # Проверка процессов-потребителей (сортировка по памяти) top -o memПримечание: На M* также помогает перезапуск WindowServer (
killall WindowServer).Для iOS:
Очистка кэша Safari и других приложений
// Очистка кэша WebKit (если используется WKWebView) WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0)) { print("Кэш очищен") } // Очистка UserDefaults (если там накопились данные) UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)Очистка временных файлов
// Удаление содержимого папки tmp if let tmpDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first { try? FileManager.default.removeItem(at: tmpDir) }Остановка анимаций и тяжелых операций
// Приостановка CoreAnimation-процессов CATransaction.begin() CATransaction.setDisableActions(true) // ... изменения UI ... CATransaction.commit() // Отмена URLSession-запросов URLSession.shared.getAllTasks { tasks in tasks.forEach { $0.cancel() } }Логирование памяти в реальном времени, данный способ будет рассмотрен ниже
Совет: На устройстве - закрыть все приложения, перезапустить Safari, отключить фоновое обновление.
Долгосрочное решение:
- Увеличить объем ОЗУ (если возможно).
- Настроить swap (для macOS).
Пример из практики:
У пользователя с 8 ГБ ОЗУ при 25% throttled наблюдались лаги даже в Finder - после purge и закрытия Chrome нагрузка упала до 3%.
Важно: Постоянно высокий % throttled - признак необходимости апгрейда железа!
Идеальные показатели (пример для iPhone 14 Pro):
Pages free: 15-20% от total RAM
Pages active: 40-50%
Pages inactive: 20-30%
Pages throttled: 0%
Pageins/Pageouts: <100/сек
Swapins/Swapouts: 0 (или минимально)
vm_stat в iOS? Link to heading
В iOS разработчик может использовать встроенные средства вроде Instruments или в Xcode, но часто надо бывает смотреть статистику потребления виртуальной памяти в рантайме у тестировщиков или бета пользователей, поэтому тут пригодится собственный класс VMStatistics. Этот модуль предоставляет доступ к информации о виртуальной памяти системы. Он собирает различные метрики, такие как размер страницы памяти, общий объем свободной памяти, доступная память, а также детализированную информацию о различных типах памяти (active, inactive, spеculative и т.д.). Модуль использует системные вызовы для получения статистики и преобразует полученные данные в удобные для использования значения (например, в мегабайты).
Необходимо учитывать, что начиная с A12 Bionic, iPhone и iPad используют быстрые NVMe-накопители с низкой задержкой и высокой скоростью чтения/записи. Более старые чипы (A11 и ниже) тоже используют NVMe, но A12 оптимизирован для эффективной работы с подкачкой благодаря улучшенному контроллеру памяти и энергопотреблению. Apple учитывает, что SSD в iPhone/iPad имеют ограниченный ресурс перезаписи, поэтому Swap активирован только на устройствах, где это не приведёт к преждевременному износу. A12 Bionic имеет более эффективный контроллер памяти, который минимизирует износ NAND-памяти при частых операциях подкачки.
Что такое VMStatistics
1import Darwin
2import Foundation
3
4/// Класс `VMDiagnostics` предоставляет доступ к статистике виртуальной памяти операционной системы.
5/// Поддерживает платформы macOS и iOS (частично), включая симулятор.
6///
7/// Использует системные вызовы `host_statistics64`, `sysctlbyname` и другие, чтобы получать
8/// данные о состоянии оперативной памяти и страниц памяти.
9public final class VMDiagnostics {
10
11 // MARK: - Public Properties
12
13 /// Размер страницы памяти в байтах.
14 ///
15 /// Значение извлекается с помощью системного вызова `host_page_size`.
16 public let pageSize: UInt64 = {
17 var result: vm_size_t = 0
18 precondition(host_page_size(mach_host_self(), &result) == KERN_SUCCESS, "host_page_size failed")
19
20 return UInt64(result)
21 }()
22
23 /// Общий объем установленной оперативной памяти в системе, в мегабайтах.
24 ///
25 /// Значение извлекается с помощью вызова `sysctlbyname("hw.memsize")`.
26 public let total: Double = {
27 var memSize: UInt64 = 0
28 var length = MemoryLayout.size(ofValue: memSize)
29 precondition(sysctlbyname("hw.memsize", &memSize, &length, nil, 0) == 0, "sysctlbyname failed")
30
31 return memSize.megabytes
32 }()
33
34 /// Оценка доступной памяти в мегабайтах.
35 ///
36 /// На macOS и в симуляторе рассчитывается как сумма `free` и `inactive`.
37 /// На iOS используется `os_proc_available_memory()`.
38 public var available: Double {
39 #if os(macOS) || targetEnvironment(simulator)
40 return free + inactive
41 #else
42 return UInt64(os_proc_available_memory()).megabytes
43 #endif
44 }
45
46 /// Объем используемой оперативной памяти в мегабайтах.
47 ///
48 /// Рассчитывается как разница между `total` и `available`.
49 public var used: Double { total - available }
50
51 /// Количество свободной памяти в мегабайтах.
52 public var free: Double { memory(\.free_count) }
53
54 /// Количество активной памяти в мегабайтах.
55 ///
56 /// Активная память содержит используемые в данный момент данные.
57 public var active: Double { memory(\.active_count) }
58
59 /// Количество неактивной памяти в мегабайтах.
60 ///
61 /// Неактивная память ранее использовалась и может быть быстро переиспользована.
62 public var inactive: Double { memory(\.inactive_count) }
63
64 /// Количество спекулятивной памяти в мегабайтах.
65 ///
66 /// Это память, предварительно выделенная системой и ещё не используемая.
67 public var speculative: Double { memory(\.speculative_count) }
68
69 /// Количество проводимой (wired) памяти в мегабайтах.
70 ///
71 /// Эта память не может быть выгружена на диск и всегда должна находиться в ОЗУ.
72 public var wired: Double { memory(\.wire_count) }
73
74 /// Объем сжатой памяти в мегабайтах.
75 ///
76 /// Система может сжимать страницы в оперативной памяти для экономии места.
77 public var compressed: Double { memory(\.compressor_page_count) }
78
79 /// Количество потоков, заблокированных из-за нехватки памяти.
80 public var throttled: UInt64 { counter(\.throttled_count) }
81
82 /// Количество операций выгрузки страниц из хранилища в память.
83 public var pageins: UInt64 { counter(\.pageins) }
84
85 /// Количество операций выгрузки страниц из памяти в хранилище.
86 public var pageouts: UInt64 { counter(\.pageouts) }
87
88 /// Количество операций чтения из swap-файла.
89 public var swapins: UInt64 { counter(\.swapins) }
90
91 /// Количество операций записи в swap-файл.
92 public var swapouts: UInt64 { counter(\.swapouts) }
93
94 /// Количество всех обращений к страницам памяти, которых не оказалось в ОЗУ.
95 public var faults: UInt64 { counter(\.faults) }
96
97 /// Количество copy-on-write (COW) ошибок страниц.
98 public var cowFaults: UInt64 { counter(\.cow_faults) }
99
100 // MARK: - Private
101
102 /// Текущее состояние структуры `vm_statistics64`, получаемое от ядра через `host_statistics64`.
103 ///
104 /// Если получение данных не удалось — возвращается `nil`.
105 private var vmStats: vm_statistics64? {
106 var size = mach_msg_type_number_t(MemoryLayout<vm_statistics64_data_t>.size / MemoryLayout<integer_t>.size)
107 var stats = vm_statistics64()
108
109 let result = withUnsafeMutablePointer(to: &stats) {
110 $0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
111 host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &size)
112 }
113 }
114
115 return result == KERN_SUCCESS ? stats : nil
116 }
117
118 /// Вспомогательная функция для получения значения счетчика памяти из `vm_statistics64`
119 /// и преобразования его в мегабайты.
120 ///
121 /// - Parameter keyPath: Ключ к нужному полю в `vm_statistics64`.
122 /// - Returns: Значение в мегабайтах (`Double`), или `0`, если получить статистику не удалось.
123 private func memory<T: BinaryInteger>(_ keyPath: KeyPath<vm_statistics64, T>) -> Double {
124 guard let stats = vmStats else { return 0 }
125
126 return (UInt64(stats[keyPath: keyPath]) * pageSize).megabytes
127 }
128
129 /// Вспомогательная функция для получения значения счётчика из `vm_statistics64`.
130 ///
131 /// - Parameter keyPath: Ключ к нужному полю в `vm_statistics64`.
132 /// - Returns: Целочисленное значение (`UInt64`), или `0`, если получить статистику не удалось.
133 private func counter<T: BinaryInteger>(_ keyPath: KeyPath<vm_statistics64, T>) -> UInt64 {
134 return vmStats.map { UInt64($0[keyPath: keyPath]) } ?? 0
135 }
136}
137
138// MARK: - UInt64 Extension for Memory Conversion
139
140private extension UInt64 {
141
142 /// Представление значения в мегабайтах.
143 ///
144 /// Преобразует байты в мегабайты (1 MB = 1,048,576 байт).
145 var megabytes: Double {
146 Double(self) / 1_048_576
147 }
148
149}
Это класс на Swift, предоставляющий доступ к ключевым метрикам виртуальной памяти:
- общий объем оперативной памяти (
total) - свободная и используемая память (
free,used,available) - активные и неактивные страницы (
active,inactive) - сжатая память, swap, page faults и др.
Все значения возвращаются в мегабайтах, а низкоуровневые вызовы скрыты под капотом.
Пример использования Link to heading
let stats = VMStatistics()
print("Всего: \(stats.total) MB")
print("Доступно: \(stats.available) MB")
print("Используется: \(stats.used) MB")
print("Сжато: \(stats.compressed) MB")
print("Pageins: \(stats.pageins)")
Как работает vmStats
Link to heading
Ключевую роль в классе VMStatistics играет приватное вычисляемое свойство:
private var vmStats: vm_statistics64? {
var size = mach_msg_type_number_t(MemoryLayout<vm_statistics64_data_t>.size / MemoryLayout<integer_t>.size)
var stats = vm_statistics64()
let result = withUnsafeMutablePointer(to: &stats) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &size)
}
}
return result == KERN_SUCCESS ? stats : nil
}
Что здесь происходит? Link to heading
1. Подготовка структуры Link to heading
Мы создаем переменную stats типа vm_statistics64, в которую ядро запишет данные. Размер структуры пересчитывается в количестве integer_t, как того требует host_statistics64.
2. Системный вызов host_statistics64
Link to heading
Функция host_statistics64 - это API ядра XNU, через который можно получить актуальное состояние виртуальной памяти. Она требует:
- дескриптор хоста (
mach_host_self()) - тип данных (
HOST_VM_INFO64) - указатель на буфер (
stats), предварительно приведённый кinteger_t* - размер буфера
Если вызов успешен, возвращается KERN_SUCCESS, и структура stats заполняется актуальными данными.
3. Безопасное обёртывание в Swift Link to heading
Все эти вызовы обёрнуты в безопасную Swift-функцию. Если данные получить не удалось - возвращается nil, и остальные методы класса корректно это обрабатывают.
Заключение Link to heading
Класс VMStatistics - инструмент для мониторинга памяти в реальном времени, который можно интегрировать в приложения для iOS и macOS. С его помощью можно:
- Отслеживать утечки памяти и предотвращать падения приложений.
- Оптимизировать потребление ОЗУ, особенно в ресурсоемких задачах (обработка видео, ML, игры).
- Автоматически реагировать на критические состояния (например, очищать кэши при нехватке памяти).
Мы разобрали основы организации виртуальной памяти в системах на базе Darwin и научились извлекать ключевые параметры, необходимые для анализа работы приложений. В следующей части мы сосредоточимся на более узком, но критически важном компоненте - стеке вызовов, рассмотрим его структуру, особенности работы и научимся отслеживать его поведение в реальном времени.