Поддержка страниц размером 16 КБ

Исторически Android поддерживал страницы памяти размером всего 4 КБ, что оптимизировало производительность системной памяти для среднего объёма памяти, обычно используемого устройствами Android. Начиная с Android 15, AOSP поддерживает устройства, настроенные на использование страниц размером 16 КБ (устройства с 16 КБ). Если ваше приложение использует библиотеки NDK , напрямую или косвенно через SDK, вам потребуется пересобрать приложение для его работы на устройствах с 16 КБ.

Поскольку производители устройств продолжают выпускать устройства с большим объёмом физической памяти (ОЗУ), многие из них будут использовать размер страницы 16 КБ (а в перспективе и больше) для оптимизации производительности. Добавление поддержки устройств с размером страницы 16 КБ позволит вашему приложению работать на этих устройствах и позволит использовать преимущества, связанные с повышением производительности. Без перекомпиляции приложения не будут работать на устройствах с 16 КБ в будущих версиях Android.

Чтобы помочь вам добавить поддержку для вашего приложения, мы предоставили рекомендации о том, как проверить, затронуто ли ваше приложение , как перестроить ваше приложение (если это применимо) и как протестировать ваше приложение в среде 16 КБ с помощью эмуляторов (включая образы системы Android 15 для эмулятора Android).

Требования к совместимости с Google Play

Чтобы приложения корректно работали в последних версиях Android, все приложения для Android 15 (уровень API 35) и более поздних версий должны поддерживать страницы памяти размером 16 КБ на 64-разрядных устройствах в Google Play. С 1 февраля 2027 года вы не сможете выпускать обновления приложения, если они не поддерживают страницы памяти размером 16 КБ.

Предупреждение в Google Play Console о том, что до 1 февраля 2027 года в обновления приложений нужно добавить поддержку страниц памяти размером 16 КБ
Рисунок 1. Предупреждение о совместимости в Google Play Console.

Преимущества и повышение эффективности

Устройства, настроенные с размером страницы 16 КБ, в среднем используют немного больше памяти, но также получают различные улучшения производительности как для системы, так и для приложений:

  • Сокращение времени запуска приложений, когда система испытывает нехватку памяти: в среднем на 3,16 % меньше, с более значительными улучшениями (до 30 %) для некоторых протестированных нами приложений.
  • Снижение энергопотребления при запуске приложения: в среднем снижение на 4,56 %.
  • Более быстрый запуск камеры: в среднем на 4,48 % более быстрый горячий запуск и в среднем на 6,60 % более быстрый холодный запуск.
  • Улучшено время загрузки системы: в среднем улучшено на 8% (приблизительно 950 миллисекунд).

Эти улучшения основаны на нашем первоначальном тестировании, и результаты на реальных устройствах, вероятно, будут отличаться. По мере продолжения тестирования мы предоставим дополнительный анализ потенциальных преимуществ для приложений.

Как проверить, затронуто ли ваше приложение

Если ваше приложение использует нативный код , вам следует пересобрать его с поддержкой устройств с памятью 16 КБ . Если вы не уверены, использует ли ваше приложение нативный код, вы можете использовать APK Analyzer для его определения, а затем проверить выравнивание сегментов ELF для любых обнаруженных общих библиотек . Android Studio также предоставляет функции, помогающие автоматически обнаруживать проблемы выравнивания .

Если ваше приложение использует только код, написанный на языке программирования Java или Kotlin, включая все библиотеки и SDK, то оно уже поддерживает устройства с 16 КБ. Тем не менее, мы рекомендуем протестировать приложение в среде с 16 КБ, чтобы убедиться в отсутствии непредвиденных регрессий в его поведении.

Используется ли в вашем приложении нативный код?

В вашем приложении используется нативный код, если выполняется любое из следующих условий:

  • В вашем приложении используется нативный код C/C++. Если в вашем приложении используется Android NDK, значит в нем есть нативный код.
  • Ваше приложение связано со сторонними нативными библиотеками или зависимостями (например, SDK), которые используют эти функции.
  • Ваше приложение создано с помощью стороннего конструктора, который использует нативные библиотеки на устройстве.

Как найти нативные библиотеки с помощью APK Analyzer

APK Analyzer – это инструмент, который позволяет оценивать различные аспекты созданного APK-файла. Чтобы проверить, использует ли приложение нативный код (независимо от того, совместим ли он со страницами памяти размером 16 КБ):

  1. Откройте Android Studio, нажмите File (Файл) > Open (Открыть) и выберите любой проект.
  2. В строке меню выберите Build (Сборка) > Analyze APK (Анализ APK).

    Пункт меню Build в Android Studio, позволяющий запустить APK Analyzer
  3. Выберите APK-файл, который хотите проанализировать.

  4. Проверьте папку lib, в которой хранятся файлы общих объектов (.so), если они есть. Если в приложении есть файлы общих объектов, значит оно использует нативный код. В столбце Выравнивание отображаются предупреждения для файлов, в которых есть проблемы с выравниванием. Если общих объектных файлов нет или отсутствует папка lib, значит в приложении не используется нативный код.

    Окно APK Analyzer, в котором показано, что общие объектные файлы присутствуют

Как выявлять проблемы с выравниванием с помощью автоматических проверок

Android Studio заранее предупредит вас, если ваши предварительно созданные библиотеки или APK несовместимы с размером страницы 16 КБ. Используйте инструмент APK Analyzer, чтобы проверить, какие библиотеки нужно обновить и требуется ли изменить код.

Предупреждения о проблемах с выравниванием в проекте Студии

Lint в Android Studio также выделяет нативные библиотеки, которые не выровнены по 16 КБ.

Предупреждение линтера Android Studio о невыровненной нативной библиотеке

Проверьте выравнивание сегментов ELF для общих библиотек

Убедитесь, что сегменты ELF общих библиотек выровнены по размеру 16 КБ.

Используйте APK Analyzer (рекомендуется)

Проще всего проверить выравнивание общих библиотек с помощью APK Analyzer в Android Studio. Если файл общего объекта не выровнен по 16 КБ, в столбце Alignment (Выравнивание) появится предупреждение.

Как использовать скрипт check_elf_alignment.sh (Linux или macOS)

Если вы разрабатываете приложение в Linux или macOS и предпочитаете использовать скрипт, вы можете использовать скрипт check_elf_alignment.sh:

  1. Сохраните скрипт check_elf_alignment.sh в файл.

  2. Запустите скрипт для APK-файла приложения:

    check_elf_alignment.sh APK_NAME.apk
    

    Скрипт выводит ALIGNED или UNALIGNED для всех общих библиотек arm64-v8a.

  3. Если какие-либо общие библиотеки arm64-v8a или x86_64 UNALIGNED, вам нужно обновить упаковку этих библиотек, а затем перекомпилировать приложение и повторно протестировать его, следуя инструкциям в этом разделе.

Использование инструментов командной строки напрямую

Чтобы проверить выравнивание сегментов ELF с помощью инструментов командной строки, выполните следующие действия:

  1. Убедитесь, что у вас установлены Android SDK Build-Tools версии 35.0.0 или более поздней и Android NDK. Для этого используйте SDK Manager в Android Studio или инструмент командной строки sdkmanager.
  2. Извлеките APK-файл приложения.

    Linux или macOS

    unzip APK_NAME.apk -d /tmp/my_apk_out
    

    Windows (PowerShell)

    Expand-Archive -Path .\APK_NAME.apk -DestinationPath ~\tmp\my_apk_out
    
  3. Во временном каталоге, в который вы распаковали APK-файл, проверьте содержимое каталога lib на наличие файлов общих объектов (.so). Это те же файлы общих объектов, которые вы видели, когда идентифицировали нативные библиотеки с помощью APK Analyzer. Выполните следующую команду для каждого файла общего объекта:

    Linux или macOS

    SDK_ROOT_LOCATION/Android/sdk/ndk/NDK_VERSION/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objdump -p SHARED_OBJECT_FILE.so | grep LOAD
    

    Windows (PowerShell)

    SDK_ROOT_LOCATION\Android\sdk\ndk\NDK_VERSION\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-objdump.exe -p SHARED_OBJECT_FILE.so | Select-String -Pattern "LOAD"
    

    где SDK_ROOT_LOCATION – путь к каталогу, в котором установлен Android SDK, SHARED_OBJECT_FILE – название проверяемого файла общего объекта, а NDK_VERSION – версия установленного Android NDK (например, 28.0.12433566). Для каждого проверяемого файла выходные данные будут выглядеть примерно так:

    LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**14
    LOAD off    0x0000000000042a90 vaddr 0x0000000000043a90 paddr 0x0000000000043a90 align 2**14
    LOAD off    0x0000000000046230 vaddr 0x0000000000048230 paddr 0x0000000000048230 align 2**14
    
  4. Проверьте выходные строки и убедитесь, что в сегментах загрузки нет значений меньше 2**14. Если для каких-либо сегментов загрузки указано значение 2**13, 2**12 или меньше, вам нужно обновить упаковку этих библиотек, а затем перекомпилировать приложение и повторно протестировать его, следуя инструкциям в этом разделе.

  5. Затем запустите инструмент командной строки zipalign для APK-файла приложения:

    Linux или macOS

    SDK_ROOT_LOCATION/Android/sdk/build-tools/35.0.0/zipalign -v -c -P 16 4 APK_NAME.apk
    

    Windows (PowerShell)

    SDK_ROOT_LOCATION\Android\sdk\build-tools\35.0.0\zipalign.exe -v -c -P 16 4 APK_NAME.apk
    

    где SDK_ROOT_LOCATION – путь к каталогу, в котором установлен Android SDK, а APK_NAME – название APK-файла приложения. Если все общие библиотеки выровнены правильно, в последней строке выходных данных будет написано "Verification successful" (Проверка выполнена успешно).

    Если проверка не пройдена, некоторые общие библиотеки нужно выровнять. Для этого обновите их упаковку, а затем перекомпилируйте приложение и повторите проверку, следуя инструкциям в этом разделе.

Проверьте флаг безопасности RELRO

Чтобы снизить риск уязвимостей, современные компоновщики используют флаг Relocation Read-Only (RELRO). Он делает целевые разделы перемещения (например, .got и .data.rel.ro) в файле общего объекта доступными только для чтения после завершения динамических перемещений. Включите флаг RELRO в сборке.

Когда сегмент GNU_RELRO использует общую страницу памяти с последующим сегментом LOAD данных, доступным для записи (например, .data или .bss), конечный адрес сегмента GNU_RELRO (VirtAddr + MemSiz) должен быть выровнен по границе 16 КБ (0x4000). В противном случае, когда компоновщик Android пометит страницу памяти размером 16 КБ, содержащую конец сегмента GNU_RELRO, как доступную только для чтения, все смежные данные, доступные для записи и находящиеся в остальной части той же страницы памяти размером 16 КБ, также станут доступными только для чтения. Это приведет к сбою приложения во время выполнения с ошибкой сегментации (SIGSEGV) при записи этих данных. Обычно это происходит, если файл .so был создан с помощью набора инструментов NDK r27 или более ранней версии без включения соответствующих флагов.

Чтобы проверить статус выравнивания сегментов RELRO в файлах общих объектов, используйте APK Analyzer в Android Studio.

APK Analyzer в Android Studio показывает выравнивание RELRO для нативных библиотек

Как использовать инструменты командной строки

Если вы хотите проверить сопоставление вручную или автоматизировать этот процесс, выполните следующие действия:

Шаг 1. Проверьте заголовки программы

Выполните следующую команду для каждого файла общего объекта (в Linux замените darwin-x86_64 на linux-x86_64):

Linux или macOS

SDK_ROOT_LOCATION/Android/sdk/ndk/NDK_VERSION/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-readelf -Wl SHARED_OBJECT_FILE.so | grep 'LOAD\|RELRO\|Type'

Windows (PowerShell)

SDK_ROOT_LOCATION\Android\sdk\ndk\NDK_VERSION\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-readelf.exe -Wl SHARED_OBJECT_FILE.so | Select-String -Pattern 'LOAD|RELRO|Type'

Строка GNU_RELRO выводится, если есть сегмент RELRO.

Шаг 2. Проверьте, совместим ли RELRO со страницей размером 16 КБ

Общий объектный файл совместим с размером страницы 16 КБ в отношении RELRO, если выполняется любое из следующих условий:

  1. В заголовках программы нет сегмента GNU_RELRO.
  2. Конечный адрес сегмента GNU_RELRO выровнен по границе 16 КБ. Сумма его виртуального адреса (VirtAddr) и размера памяти (MemSiz), деленная на 16 КБ (0x4000), дает остаток (модуль), равный нулю: (VirtAddr + MemSiz) % 0x4000 == 0.
  3. За GNU_RELRO в том же сегменте LOAD нет данных, доступных для записи. GNU_RELRO занимает всю оставшуюся часть содержащего его сегмента LOAD (то есть LOAD.VirtAddr + LOAD.MemSiz == RELRO.VirtAddr + RELRO.MemSiz), поэтому на последней странице нет доступных для записи разделов .data или .bss.

Примеры

В примерах ниже показано, как оценивать выходные данные llvm-readelf для разных конфигураций GNU_RELRO.

Пример 1. Несоответствующий файл .so (не соответствует требованиям)

В этом примере GNU_RELRO использует сегмент RW LOAD совместно с последующими данными, доступными для записи, а его конечный адрес не кратен 0x4000:

Type           Offset   VirtAddr           PhysAddr           FileSiz MemSiz  Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x0cfaf0 0x0cfaf0 R E 0x4000
LOAD           0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x02510 0x04510 RW  0x4000
GNU_RELRO      0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x01510 0x01510 R   0x1
  • Формула: (VirtAddr + MemSiz) % 0x4000 == 0
  • Результат: (0xdfaf0 + 0x01510) % 0x4000 = 0xE1000 % 0x4000 == 0x1000

Поскольку значение 0x1000 не равно нулю, а содержащий его сегмент LOAD продолжается после 0xE1000 (заканчиваясь на 0xdfaf0 + 0x04510 = 0xE4000), этот файл .so несовместим со страницами памяти размером 16 КБ. На устройстве с размером страницы 16 КБ компоновщик округляет диапазон защиты RELRO до границ страницы 16 КБ, помечая диапазон от 0xDC000 (предыдущий разрыв страницы) до 0xE4000 как доступный только для чтения. Однако разделы .data и .bss файла .so, доступные для записи, находятся в поддиапазоне от 0xE1000 до 0xE4000, что приводит к ошибке сегментации при изменении во время выполнения. Пересоберите файл .so, как описано в разделе Как скомпилировать приложение с выравниванием ELF размером 16 КБ.

Пример 2. Выровненный файл .so с сегментом RELRO (соответствует требованиям)

В этом примере конец сегмента GNU_RELRO приходится на границу страницы размером 16 КБ:

Type           Offset   VirtAddr           PhysAddr           FileSiz MemSiz  Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x0cfaf0 0x0cfaf0 R E 0x4000
LOAD           0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x01510 0x03510 RW  0x4000
GNU_RELRO      0x0cfaf0 0x00000000000dfaf0 0x00000000000dfaf0 0x00510 0x00510 R   0x1
  • Формула: (VirtAddr + MemSiz) % 0x4000 == 0
  • Результат: (0xdfaf0 + 0x00510) % 0x4000 = 0xE0000 % 0x4000 == 0

Поскольку остаток равен 0, защита RELRO заканчивается точно на границе страницы 0xE0000, не затрагивая данные, доступные для записи, начиная с 0xE0000.

Пример 3. Файл .so без сегмента RELRO (соответствует требованиям)
Type           Offset   VirtAddr           PhysAddr           FileSiz MemSiz  Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000680 0x000680 R E 0x4000
LOAD           0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x0001d8 RW  0x4000

В этом файле .so нет сегмента GNU_RELRO, поэтому проблемы с границами страниц RELRO не возникают (хотя мы все равно рекомендуем включить RELRO в целях безопасности).

Пример 4. GNU_RELRO в собственном сегменте LOAD (соответствует правилам)

В этом примере сегмент GNU_RELRO не заканчивается на границе 16 КБ, но он все равно соответствует требованиям, поскольку за ним в том же сегменте LOAD не следуют данные, доступные для записи:

Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000680 0x000680 R E 0x4000
LOAD           0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x000980 RW  0x4000
GNU_RELRO      0x000680 0x0000000000004680 0x0000000000004680 0x0001d8 0x000980 R   0x1
  • Формула: (VirtAddr + MemSiz) % 0x4000 == 0
  • Результат: (0x4680 + 0x0980) % 0x4000 = 0x5000 % 0x4000 == 0x1000

Хотя значение 0x1000 не равно нулю, этот файл .so по-прежнему совместим с размером страницы 16 КБ. Сравнивая GNU_RELRO с содержащим его сегментом LOAD (вторая запись LOAD), мы видим, что оба начинаются с VirtAddr 0x4680 и заканчиваются по одному и тому же адресу (0x4680 + 0x0980 = 0x5000).

Поскольку весь сегмент LOAD содержит только данные RELRO, в поддиапазоне от 0x5000 до 0x8000 нет доступных для записи данных .data или .bss (либо в библиотеке нет доступных для записи данных, не относящихся к RELRO, либо любой последующий доступный для записи сегмент LOAD начинается на отдельной границе страницы размером 16 КБ по адресу 0x8000 или за его пределами). Защита компоновщика от записи не будет перекрываться с данными, доступными для записи, поэтому ошибка сегментации не возникнет.

Как создать приложение с поддержкой устройств со страницами памяти размером 16 КБ

Если в вашем приложении используется нативный код, выполните действия, описанные в разделах ниже, чтобы обеспечить поддержку устройств со страницами памяти размером 16 КБ.

  1. Обновите упаковку общих библиотек
  2. Скомпилируйте приложение с выравниванием ELF-файлов по границе 16 КБ
  3. Исправление кода и устранение проблем во время выполнения
  4. Оптимизируйте пользовательские распределители памяти (если применимо)
  5. Как проверить, поддерживают ли SDK размер страницы 16 КБ

Обновите упаковку общих библиотек

Перейдите на AGP версии 8.5.1 или выше и используйте несжатые общие библиотеки.

Используйте bundletool, чтобы проверить выравнивание молнии

Чтобы посмотреть, как выровнен набор, используйте:

bundletool dump config --bundle=<my .aab>  | grep alignment

Если вы видите PAGE_ALIGNMENT_16K, значит ваш пакет запрашивает выравнивание ZIP-архива по границе 16 КБ. Если вы видите PAGE_ALIGNMENT_4K, это означает, что в APK, созданном из этого AAB, файлы .so в ZIP-архиве должны быть выровнены по 4 КБ.

AGP версии 8.5.1 или более поздней;

На устройствах с размером страницы 16 КБ приложения, поставляемые с несжатыми общими библиотеками, должны выравнивать их по границе 16 КБ. Для этого необходимо обновить плагин Android Gradle (AGP) до версии 8.5.1 или более поздней. Подробную информацию о процессе обновления можно найти в разделе Помощник по обновлению плагина Android Gradle.

AGP версии 8.5 или более ранней

Если вы не можете обновить AGP до версии 8.5.1 или выше, перейдите на сжатые общие библиотеки. Обновите конфигурацию Gradle, чтобы Gradle сжимал общие библиотеки при упаковке приложения. Это поможет избежать проблем с установкой приложения, связанных с неоптимизированными общими библиотеками.

Яркий

Добавьте в файл build.gradle следующий параметр:

android {
  ...
  packagingOptions {
      jniLibs {
        useLegacyPackaging true
      }
  }
}

Kotlin

Добавьте в файл build.gradle.kts следующий параметр:

android {
  ...
  packagingOptions {
      jniLibs {
        useLegacyPackaging = true
      }
  }
}
AGP версии 8.0 или ниже

Если вы используете AGP версии 8.0 или более ранней, вам также нужно отключить параметр несжатой нативной библиотеки для наборов App Bundle в файле gradle.properties:

android.bundle.enableUncompressedNativeLibs=false

Как скомпилировать приложение с выравниванием ELF-файлов по размеру 16 КБ

Чтобы приложение работало на устройствах со страницами памяти размером 16 КБ, сегменты ELF общих библиотек должны быть выровнены по размеру 16 КБ.

Если вы разработчик игр и ваша игра работает на игровом движке Unity, ознакомьтесь с руководством по Unity. Если ваша игра работает на игровом движке Unreal, ознакомьтесь с руководством по Unreal.Если вы используете нативный игровой движок, продолжайте читать это руководство.

Чтобы скомпилировать приложение с выравниванием ELF-файлов по размеру 16 КБ, выполните действия из одного из разделов ниже в зависимости от используемой версии Android NDK.

Android NDK r28 или более поздней версии;

NDK версии r28 и выше по умолчанию компилирует библиотеки с выравниванием по 16 КБ.

Android NDK r27 и более ранних версий

Чтобы скомпилировать общие библиотеки, выровненные по размеру 16 КБ, с помощью Android NDK версии r27 или ниже, используйте следующие флаги компоновщика:

-Wl,-z,max-page-size=16384
-Wl,-z,common-page-size=16384

Чтобы обновить файлы конфигурации системы сборки, выполните следующие действия:

ndk-build

Если вы используете ndk-build, обновите файл Android.mk, чтобы включить выравнивание ELF-файлов по границе 16 КБ:

LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384

CMake

Если вы используете CMake, обновите файл CMakeLists.txt, чтобы включить выравнивание ELF по размеру 16 КБ:

target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
    "-Wl,-z,max-page-size=16384"
    "-Wl,-z,common-page-size=16384"
)

Исправление кода и устранение проблем во время выполнения

Даже если ваше приложение совместимо с размером страницы 16 КБ, в нем могут возникать ошибки, если в коде предполагается, что на устройстве используются страницы памяти определенного размера. Чтобы избежать этого, выполните следующие действия:

  1. Удалите все жестко закодированные зависимости, которые ссылаются на константу PAGE_SIZE, или экземпляры в логике кода, которые предполагают, что размер страницы памяти устройства составляет 4 КБ (4096).

    Вместо него необходимо использовать getpagesize() или sysconf(_SC_PAGESIZE).

  2. Найдите, где используются mmap() и другие API, требующие аргументов с выравниванием по страницам, и при необходимости замените их альтернативными вариантами.

В некоторых случаях, если в приложении используется значение PAGE_SIZE, не связанное с размером страницы, это не приведет к сбою в режиме 16 КБ. Однако если это значение передается ядру с помощью mmap без MAP_FIXED, ядро все равно использует всю страницу, что приводит к потере памяти. Поэтому при включении режима 16 КБ в NDK r27 и более поздних версиях PAGE_SIZE не определяется.

Если ваше приложение использует PAGE_SIZE таким образом и никогда не передает это значение напрямую ядру, то вместо PAGE_SIZE создайте новую переменную с новым именем, чтобы отразить, что она используется для других целей и не отражает реальную страницу памяти.

Оптимизируйте собственные распределители памяти

В системах с размером страницы 16 КБ наименьшая единица физической памяти, которую выделяет операционная система, в четыре раза больше, чем в системах с размером страницы 4 КБ. Если пользовательский распределитель был разработан с учетом предположений о 4 КБ, он может разбрасывать небольшие объекты по нескольким страницам размером 16 КБ и без необходимости сохранять пустую память. Это может существенно увеличить использование физической памяти (RSS) и снизить эффективность сжатой подкачки (ZRAM).

Если ваш код управляет собственными пулами памяти, следуйте этим рекомендациям:

1. Не задавайте в коде пороговые значения в байтах для освобождения памяти

Многие распределители используют фиксированные ограничения в байтах, чтобы решить, когда возвращать память ОС с помощью madvise(MADV_DONTNEED) (например, возвращать память, только если активные объекты занимают менее 8 КБ).

В системе с размером страницы 16 КБ даже один активный объект размером 16 байт занимает всю страницу размером 16 КБ (что больше 8 КБ). В результате порог освобождения никогда не достигается, и распределитель никогда не возвращает окружающую неиспользуемую память ядру.

Что делать. Никогда не используйте заданные в коде константы байтов для эвристики освобождения страниц. Динамически масштабировать пороговые значения для освобождения памяти во время выполнения на основе фактического размера страницы с помощью sysconf(_SC_PAGESIZE).

2. Сначала заполнять уже используемые страницы (плотное распределение)

Если распределитель выделяет память по принципу FIFO или циклического перебора, новые выделения распределяются по множеству частично заполненных страниц размером 16 КБ. Один объект на странице сохраняет все 16 КБ в физической ОЗУ.

Что делать. Всегда выделяйте новые объекты из наиболее полной (плотной) страницы или блока, прежде чем использовать пустые или малоиспользуемые страницы. Если новые объекты будут размещаться на уже занятых страницах, то на страницах, которые используются редко, количество активных объектов со временем снизится до нуля. В результате вся страница размером 16 КБ будет освобождена для ОС.

3. Выравнивайте пулы памяти по 16 КБ и не делайте их слишком большими

Многостраничные диапазоны, разработанные для систем с размером страницы 4 КБ, могут содержать чрезмерный объем неиспользуемой памяти в ядрах с размером страницы 16 КБ. Кроме того, классы размеров, которые не делятся на 16 КБ без остатка, вызывают фрагментацию в конце каждой страницы.

Что нужно сделать:

  • Убедитесь, что все пулы памяти, границы slab-аллокатора и выравнивания буфера кратны размеру страницы во время выполнения.
  • Пересмотрите размеры многостраничных диапазонов для классов малых объектов, чтобы избежать выделения слишком больших блоков, которые удерживают неиспользуемую память.

4. Немедленно освобождать физическую память для кешированных больших буферов

Пользовательские распределители часто кешируют большие буферы (> 64 КБ) в пуле в памяти, чтобы их можно было повторно использовать без дополнительных затрат на системные вызовы mmap или munmap. Однако хранение этих грязных буферов в памяти тратит мегабайты физической ОЗУ в ожидании таймера вытеснения.

Что делать. Сохраните диапазон адресов виртуальной памяти, зарезервированный для быстрого повторного использования, но вызывайте madvise(..., MADV_DONTNEED) или madvise(..., MADV_FREE) сразу после возврата буфера в кеш. Операционная система сразу же освобождает физическую оперативную память, а ваше приложение может мгновенно повторно использовать виртуальный адрес без перераспределения.

5. Обнуление памяти при освобождении, а не при выделении для сжатия ZRAM.

Android использует сжатый файл подкачки (ZRAM), чтобы хранить фоновые приложения в памяти. На устройствах с размером страниц 16 КБ, если на странице есть хотя бы один активный объект, вся страница размером 16 КБ остается резидентной или переносится в ZRAM. Оставшийся мусор (например, устаревшие указатели и строки) в ранее освобожденных частях страницы плохо сжимается.

Если ваш распределитель обнуляет память (например, для безопасности или выделения с нулевой инициализацией), рассмотрите возможность обнуления при освобождении (free()), а не при выделении:

  • Закрепленные страницы занимают меньше места. Неиспользуемая память на частично заполненных страницах размером 16 КБ сжимается в ZRAM почти до нуля, поэтому закрепленные страницы не занимают физическое пространство подкачки.
  • Минимальная нагрузка на кеш. При вызове free() память уже находится в кеше ЦП, что позволяет избежать дополнительных промахов кеша в дальнейшем.

Сводка рекомендаций

Район Рекомендация Ожидаемый эффект
Пороговые значения для удаления Динамически масштабируйте пороговые значения для выпуска с помощью sysconf(_SC_PAGESIZE). Предотвращает постоянную блокировку логики выпуска.
Порядок распределения Сначала выделять память из наиболее плотно заполненной (почти полной) страницы или блока. Уменьшает резидентную память (RSS), упаковывая активные объекты вместе.
Размер диапазона Выровняйте классы размеров по кратным 16 КБ и избегайте слишком больших блоков. Устраняет фрагментацию хвостовых страниц и уменьшает неиспользуемую память.
Кеши буфера При кешировании больших буферов сразу вызывайте madvise(MADV_DONTNEED). Устраняет раздувание ОЗУ в режиме ожидания, сохраняя при этом быстрое повторное использование виртуальной памяти.
Файл подкачки/ZRAM Обнулять память при free(), а не при выделении. Улучшает степень сжатия ZRAM, чтобы закрепленные страницы занимали меньше места.

Проверьте, поддерживают ли SDK размер страниц 16 КБ

Многие SDK совместимы со страницами размером 16 КБ, особенно если вы создаете их самостоятельно или используете последние готовые сборки. Однако некоторые предварительно созданные SDK или версии SDK несовместимы с размером страницы 16 КБ. Чтобы узнать, какую версию использовать, посетите сайт поставщика SDK.

Как протестировать приложение в среде с поддержкой страниц размером 16 КБ

После того как вы добавите в приложение поддержку устройств со страницами памяти размером 16 КБ, протестируйте его в среде с такими страницами, чтобы проверить, не возникло ли регрессий. Для этого выполните следующие действия:

  1. Настройте Android 15 SDK или более поздней версии.

  2. Настройте одну из следующих тестовых сред:

  3. Запустите тестовое устройство и выполните следующую команду, чтобы убедиться, что оно использует среду с поддержкой страниц размером 16 КБ:

    adb shell getconf PAGE_SIZE
    

    Команда должна вернуть значение 16384.

  4. Чтобы проверить, выровнено ли приложение по размеру страницы 16 КБ, выполните следующую команду zipalign, где APK_NAME – название APK-файла приложения:

    zipalign -c -P 16 -v 4 APK_NAME.apk
    
  5. Тщательно протестируйте приложение, уделяя особое внимание областям, на которые может повлиять изменение экземпляров кода, ссылающихся на определенные размеры страниц.

Как настроить эмулятор Android с образом системы на основе 16 КБ

Чтобы настроить среду с поддержкой страниц размером 16 КБ в эмуляторе Android, выполните следующие действия:

  1. В Android Studio нажмите Tools (Инструменты) > SDK Manager.
  2. На вкладке SDK Platforms (Платформы SDK) выберите Show Package Details (Показать сведения о пакете), разверните раздел Android VanillaIceCream или более поздней версии и выберите один или оба следующих системных образа эмулятора в зависимости от того, какие виртуальные устройства вы хотите создать:

    • Экспериментальный системный образ ARM 64 v8a с размером страницы 16 КБ для API Google
    • Google APIs Experimental 16 KB Page Size Intel x86_64 Atom System Image
    Как скачать системные образы эмулятора с поддержкой страниц размером 16 КБ с помощью SDK Manager в Android Studio
  3. Нажмите Apply (Применить) > OK, чтобы скачать выбранные образы системы.

  4. Следуйте инструкциям по настройке виртуального устройства для Android 15. Когда вам будет предложено выбрать образ системы, выберите скачанный образ с поддержкой страниц размером 16 КБ. Если он не рекомендуется автоматически, вы можете найти образ системы с размером страницы 16 КБ на вкладке Other Images (Другие образы).

    Найдите образ эмулятора с поддержкой страниц размером 16 КБ на вкладке Other Images (Другие образы)

Запустите эмулятор

После настройки эмулятора Android и виртуальных устройств запустите эмулятор из меню целевого устройства или из командной строки.

Включите режим 16 КБ на устройстве с помощью параметров разработчика.

Включите параметр разработчика «Загрузка с размером страницы 16 КБ» , чтобы загрузить устройство в режиме 16 КБ.

В версиях Android 15 QPR можно использовать режим разработчика , доступный на некоторых устройствах, чтобы загрузить устройство в режиме 16 КБ и выполнить тестирование на устройстве. Перед использованием режима разработчика перейдите в Настройки > Система > Обновления ПО и примените все доступные обновления.

Данная опция для разработчиков доступна на следующих устройствах:

  • Pixel 8 и 8 Pro (с Android 15 QPR1 или выше)

  • Pixel 8a (с Android 15 QPR1 или выше)

  • Pixel 9, 9 Pro и 9 Pro XL (с Android 15 QPR2 или выше)

  • Pixel 9a (с Android 16 или выше)

Режим обратной совместимости с размером страницы 16 КБ

Предупреждение в режиме совместимости с размером страницы

Предупреждение в режиме совместимости с размером страницы

Режим обратной совместимости с размером страницы 16 КБ доступен, когда на устройстве используется ядро с размером страницы 16 КБ. Менеджер пакетов запускает приложение в режиме обратной совместимости со страницами памяти размером 16 КБ, если выполняются следующие условия:

  • Если в приложении есть файлы ELF (с расширением .so) с выравниванием сегмента LOAD, равным 4 КБ.
  • Если в ZIP-архиве APK-файла есть несжатые ELF-файлы, выровненные по границе 4 КБ.

Если менеджер пакетов включил для приложения режим обратной совместимости с 16 КБ, при первом запуске приложения появится предупреждение о том, что оно работает в этом режиме.

Режим обратной совместимости позволяет запускать некоторые приложения, но для лучшей надежности и стабильности их все равно нужно оптимизировать для поддержки страниц памяти размером 16 КБ.

На странице с информацией о приложении в разделе Дополнительно включите или отключите параметр Запускать приложение в режиме совместимости с размером страницы, чтобы активировать или деактивировать режим обратной совместимости со страницами размером 16 КБ для определенного приложения. Этот параметр виден, только если на устройстве установлен размер страницы 16 КБ.

Настройка режима совместимости с размером страницы

Настройка режима совместимости с размером страницы

Чтобы принудительно включить обратную совместимость со страницами памяти размером 16 КБ для всех приложений на устройстве, выполните следующую команду:

adb shell setprop bionic.linker.16kb.app_compat.enabled true
adb shell setprop pm.16kb.app_compat.disabled false

Чтобы принудительно отключить обратную совместимость 16 КБ для всех приложений на устройстве, выполните следующую команду:

adb shell setprop bionic.linker.16kb.app_compat.enabled false
adb shell setprop pm.16kb.app_compat.disabled true

В Android 17 вы также можете принудительно отключить обратную совместимость с 16 КБ для каждого приложения и заставить любой несовместимый двоичный файл немедленно прервать работу:

    adb shell setprop bionic.linker.16kb.app_compat.enabled fatal
    adb shell setprop pm.16kb.app_compat.disabled true

Чтобы включить или отключить режим обратной совместимости для определенного приложения, задайте для свойства android:pageSizeCompat значение enabled или disabled в файле AndroidManifest.xml. Если это свойство задано, приложение не будет показывать предупреждения о режиме обратной совместимости при запуске.