Знакомая ситуация: Mac начинает подтормаживать, открываешь монитор — свободно всего пара сотен мегабайт. Первая мысль: память кончилась, надо чистить. Многие ставят «чистилку», которая по таймеру запускает purge, и часто становится только хуже.
Проблема в том, что «свободная память» — не та цифра. Разберём, почему на macOS она почти ничего не говорит, что такое memory pressure, как его прочитать из Swift в пару строк — и почему в итоге я написал свой монитор с открытым кодом.
Почему мало свободной памяти — это нормально
В macOS (как и в любой современной ОС) пустая память — это впустую потраченная память. Ядро складывает в неё кэш файлов: если вы снова откроете то же приложение или документ, его не придётся читать с диска. Когда память понадобится приложению, эти страницы освободятся мгновенно.
Activity Monitor делит память на несколько частей:
|
Часть |
Что это |
Можно ли быстро освободить |
|---|---|---|
|
App Memory |
Анонимная память процессов |
Нет |
|
Wired |
Память ядра и драйверов |
Нет |
|
Compressed |
Страницы, сжатые компрессором вместо свопа |
Нет |
|
Cached Files |
Кэш файлов |
Да, сразу |
«Memory Used» в Activity Monitor — это сумма первых трёх строк. А «свободная» память маленькая потому, что всё остальное занял кэш. Поэтому цифра Free не отвечает на вопрос «хватает ли мне памяти?».
Из Swift те же числа можно получить через Mach API:
import Darwinfunc memoryUsed() -> UInt64? { var stats = vm_statistics64() var count = mach_msg_type_number_t( MemoryLayout<vm_statistics64_data_t>.size / MemoryLayout<integer_t>.size) let kr = withUnsafeMutablePointer(to: &stats) { $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &count) } } guard kr == KERN_SUCCESS else { return nil } let page = UInt64(vm_kernel_page_size) let app = UInt64(stats.internal_page_count - stats.purgeable_count) let wired = UInt64(stats.wire_count) let compressed = UInt64(stats.compressor_page_count) return (app + wired + compressed) * page // близко к «Memory Used»}
Memory pressure: единственная честная метрика
На вопрос «хватает ли памяти» отвечает не Free, а давление на память (memory pressure). Это оценка самого ядра: насколько тяжело ему сейчас находить свободные страницы. Пока кэша много, давление нормальное, даже если Free почти ноль. Когда кэш израсходован и система начинает активно сжимать и свопить, давление растёт.
Уровень давления отдаёт sysctl:
import Darwin/// 1 — normal, 2 — warning, 4 — criticalfunc memoryPressureLevel() -> Int32 { var level: Int32 = 0 var size = MemoryLayout<Int32>.size sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &size, nil, 0) return level}
Или из терминала: sysctl kern.memorystatus_vm_pressure_level. Если нужно реагировать на изменения без опроса, есть DispatchSource.makeMemoryPressureSource.
Практическое правило: пока давление «normal», памяти хватает, сколько бы гигабайт ни показывало Used. Если оно часто уходит в «warning» или «critical», а своп растёт (sysctl vm.swapusage), тогда памяти действительно не хватает.
Почему purge по таймеру вредит
/usr/sbin/purge сбрасывает дисковый кэш. Цифра Free сразу растёт, и кажется, что стало лучше. Но теперь всё, что было в кэше, придётся снова читать с диска, и через пару минут память снова заполнится тем же самым. При нормальном давлении purge не даёт ничего, кроме лишнего I/O.
Единственная ситуация, когда он может помочь, — критическое давление, когда система уже тормозит и нужно быстро освободить место. Поэтому в своём мониторе я сделал так: чистка только по кнопке или автоматически, но исключительно при уровне critical (и не чаще раза в минуту). По умолчанию авточистка выключена.
Что получилось: Mac Monitor
Из всего этого вырос маленький монитор для меню-бара — Mac Monitor. В меню-баре одно число: скорость сети, RAM % или CPU %. По клику — панель с давлением памяти, составом (App / Wired / Compressed / Cached), свопом, загрузкой по ядрам, сетью и диском.
-
История за 24 часа. Графики RAM и CPU с минутными агрегатами и счётчиком минут критического давления; история переживает перезапуск.
-
Никаких сетевых запросов. В коде нет URLSession, аналитики и проверки обновлений. Скорость сети считается по счётчикам интерфейсов (
getifaddrs). -
Нативно и легко. SwiftUI + AppKit, около 2 000 строк, без сторонних зависимостей. Примерно 1% CPU и ~50 МБ памяти. Тяжёлые замеры (список процессов, GPU, батарея) идут только пока открыто окно, где они показаны.
-
Русский интерфейс (а также английский и узбекский), переключается без перезапуска.
Приложение бесплатное, лицензия MIT. Установка — DMG из релизов или Homebrew. Оно не нотаризовано (нет платного аккаунта разработчика), поэтому при первом запуске нужно нажать «Open Anyway» — или собрать из исходников за минуту.
Буду рад обратной связи, особенно по подсчёту памяти: если цифры расходятся с Activity Monitor на вашем Mac, напишите в комментариях или в Issues. Если проект покажется полезным — звезда на GitHub поможет другим его найти.
ссылка на оригинал статьи https://habr.com/ru/articles/1104084/