Почему «свободная RAM» на macOS врёт и на что смотреть вместо неё habr.com
Знакомая ситуация: Mac начинает подтормаживать, открываешь монитор — свободно всего пара сотен мегабайт. Первая мысль: память кончилась, надо чистить. Многие ставят «чистилку», которая по таймеру запускает purge, и часто становится только хуже.
Проблема в том, что «свободная память» — не та цифра. Разберём, почему на macOS она почти ничего не говорит, что такое memory pressure, как его прочитать из Swift в пару строк — и почему в итоге я написал свой монитор с открытым кодом.
Почему мало свободной памяти — это нормально
В macOS (как и в любой современной ОС) пустая память — это впустую потраченная память. Ядро складывает в неё кэш файлов: если вы снова откроете то же приложение или документ, его не придётся читать с диска. Когда память понадобится приложению, эти страницы освободятся мгновенно.
Activity Monitor делит память на несколько частей:
Часть
Что это
Можно ли быстро освободить
App Memory
Анонимная память процессов
Нет
Wired
Память ядра и драйверов
Нет
Compressed
Страницы, сжатые компрессором вместо свопа
Нет
Cached Files
Кэш файлов
Да, сразу
«Memory Used» в Activity Monitor — это сумма первых трёх строк. А «свободная» память маленькая потому, что всё остальное занял кэш. Поэтому цифра Free не отвечает на вопрос «хватает ли мне памяти?».
Из Swift те же числа можно получить через Mach API:
import Darwin
func memoryUsed() -> UInt64? {
var stats = vm_statistics64()
var count = mach_msg_type_number_t(
MemoryLayout.size / MemoryLayout.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 — critical
func memoryPressureLevel() -> Int32 {
var level: Int32 = 0
var size = MemoryLayout.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), свопом, загрузкой по ядрам, сетью и диском.
Панель Mac Monitor в меню-баре
История за 24 часа. Графики RAM и CPU с минутными агрегатами и счётчиком минут критического давления; история переживает перезапуск.
Никаких сетевых запросов. В коде нет URLSession, аналитики и проверки обновлений. Скорость сети считается по счётчикам интерфейсов (getifaddrs).
Нативно и легко. SwiftUI + AppKit, около 2 000 строк, без сторонних зависимостей. Примерно 1% CPU и ~50 МБ памяти. Тяжёлые замеры (список процессов, GPU, батарея) идут только пока открыто окно, где они показаны.
Русский интерфейс (а также английский и узбекский), переключается без перезапуска.
Приложение бесплатное, лицензия MIT. Установка — DMG из релизов или Homebrew. Оно не нотаризовано (нет платного аккаунта разработчика), поэтому при первом запуске нужно нажать «Open Anyway» — или собрать из исходников за минуту.
Буду рад обратной связи, особенно по подсчёту памяти: если цифры расходятся с Activity Monitor на вашем Mac, напишите в комментариях или в Issues. Если проект покажется полезным — звезда на GitHub поможет другим его найти.
Ссылка: https://github.com/Ahmadjondev/MacMonitor
Голоса участников
За 7
Против 0
Пока нет голосов
Комментарии 0
Войдите, чтобы участвовать в обсуждении.
Пока никто не написал
Станьте первым в обсуждении.