Android 16 KB Page Size: A Complete Developer’s Guide to Faster Apps

Table of Contents

Most Android performance improvements land as a framework update or a new API. This one is different. Starting with Android 15, Google added support for a 16 KB page size on ARM64 devicesā€Šā€”ā€Šand with Android 16, it’s becoming a hard requirement for apps that target new hardware.

If you haven’t looked into this yet, now is a good time. Apps that ship 4 KB-aligned native libraries will fail to load on 16 KB page-size devices. The failure isn’t gracefulā€Šā€”ā€Šit’s an UnsatisfiedLinkError and a crash.

This guide covers what the change is, which apps are affected, how to check your own APK, and what to actually do about it.

Memory Pages: A Quick Refresher

The OS doesn’t allocate memory one byte at a timeā€Šā€”ā€Šit works in fixed-size blocks called pages. For decades, Android (like most Linux systems) used a 4 KB page size. That made sense when RAM was limited and apps were simpler.

Modern flagship devices are a different story. They have multiple gigabytes of RAM, 64-bit ARM processors, and apps that load dozens of native libraries at startup. Managing all of that in 4 KB chunks means more page table entries, more TLB pressure, and more overhead on every app launch.

16 KB pages reduce that overhead. The OS manages fewer, larger chunksā€Šā€”ā€Šfewer page faults at startup, fewer TLB misses during execution, and less kernel bookkeeping overall.

Why Google Made theĀ Change

The performance case is real:

Faster cold starts. Fewer pages need to be mapped during app startup. Google’s benchmarks showed cold launch improvements of up to 30% on devices running a 16 KB page-size kernel.

Better TLB efficiency. The TLB (Translation Lookaside Buffer) is a small hardware cache that maps virtual addresses to physical memory. With 16 KB pages, each TLB entry covers four times more memory, which means fewer misses on cache-heavy operations.

Less kernel overhead. Fewer pages means a smaller page table. The kernel spends less time on memory management and more time running your code.

Industry alignment. Apple has used 16 KB pages on ARM devices for years. The mainline Linux kernel has progressively added support too. Android isn’t ahead of the curve hereā€Šā€”ā€Šit’s catching up.

Where Things Stand inĀ 2026

  • Android 15 introduced 16 KB page size support in the emulator so developers could start testing.
  • Android 16 is expected to require 16 KB compliance for apps targeting API 36 on supported hardware.
  • Pixel 9 and later are expected to ship with kernels configured for 16 KB pages.
  • Play Console already shows warnings for apps that bundle 4 KB-alignedĀ .so files when targeting API 35+.

The install base of 16 KB devices is still small, but it will grow quickly as new flagships ship. Getting ahead of this now is much easier than scrambling when Play starts rejecting updates.

Does This Affect YourĀ App?

It depends entirely on whether your app includes native code.

Pure Kotlin or JavaĀ apps

You’re largely fine. The Android Runtime handles .dex alignment automatically, so managed code isn’t affected. The one thing to watch is third-party SDKsā€Šā€”ā€Šthey sometimes bundle native .so files you didn’t write and may not have checked.

Apps with NDK or native libraries

This is where the requirement has real teeth. If your app includes:

  • Native libraries (.so files) built with the NDK
  • Pre-builtĀ .so files from third-party SDKs
  • A game engine like Unity or Cocos2d
  • Audio, video, or image processing libraries with native bindings

…then every one of those .so files needs to be compiled with 16 KB-aligned ELF segments. If any aren’t, the OS on a 16 KB device will refuse to load them.

Check YourĀ APK

Before touching any build config, find out where you actually stand.

Use readelf on yourĀ .soĀ files

Bash
# Unzip the APK
unzip your-app.apk -d app-contents

# Inspect a native library
readelf -l app-contents/lib/arm64-v8a/libyourlibrary.so | grep LOAD

Look at the alignment column on the right side of each LOAD segment line:

  • 0x4000 = 16384 bytes = 16 KB compliant
  • 0x1000 = 4096 bytes = 4 KB needs recompiling

Compliant output:

LOAD  0x000000 ... 0x001abc 0x001abc R   0x4000
LOAD 0x002000 ... 0x005def 0x005def R E 0x4000

Non-compliant output:

LOAD  0x000000 ... 0x001abc 0x001abc R   0x1000

Do this for every .so in the APK, not just the ones you wrote. Third-party libraries need to pass too.

Run AGP’s built-in lintĀ check

Android Gradle Plugin 8.5+ includes a lint check specifically for this. Run:

./gradlew lint

Look for warnings tagged PageSizeAlignment. They’ll call out each non-compliant library by name.

Fix Your Own Native Libraries

If you maintain native code with the NDK, the fix is a single linker flag.

With CMake

CMake
# CMakeLists.txt

cmake_minimum_required(VERSION 3.22.1)
project(MyNativeLib)

add_library(
    mynativelib
    SHARED
    src/main/cpp/mynativelib.cpp
)

# Tell the linker to align ELF LOAD segments to 16 KB boundaries
target_link_options(mynativelib PRIVATE "-Wl,-z,max-page-size=16384")

find_library(log-lib log)

target_link_libraries(
    mynativelib
    ${log-lib}
)

The flag -Wl,-z,max-page-size=16384 passes max-page-size=16384 directly to the linker. It sets the alignment of every LOAD segment in the output .so to 16 KB. That’s all the change requires on your end.

With ndk-build

CMake
# Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := mynativelib
LOCAL_SRC_FILES := mynativelib.cpp

# 16 KB page size alignment
LOCAL_LDFLAGS   := -Wl,-z,max-page-size=16384

include $(BUILD_SHARED_LIBRARY)

After rebuilding, re-run the readelf check to confirm the alignment value changed from 0x1000 to 0x4000.

One thing worth knowing: a 16 KB-aligned .so runs fine on 4 KB devices too. The extra alignment padding is harmless on older hardware. You don’t need separate buildsā€Šā€”ā€Šone .so covers both.

Kotlin: What You Need toĀ Handle

Kotlin doesn’t control ELF alignment, but there are places where Kotlin code loads native libraries and should handle failures gracefully.

Safe native libraryĀ loading

System.loadLibrary() throws UnsatisfiedLinkError if a .so fails to loadā€Šā€”ā€Šwhich on a 16 KB device usually means the library isn’t aligned. Without handling this, the app just crashes.

Kotlin
// NativeLibraryLoader.kt

object NativeLibraryLoader {

    private const val TAG = "NativeLibraryLoader"

    /**
     * Loads a native library and returns false (instead of crashing)
     * if it fails. On 16 KB page-size devices, an UnsatisfiedLinkError
     * usually means the .so wasn't compiled with max-page-size=16384.
     */
    fun loadSafely(libraryName: String): Boolean {
        return try {
            System.loadLibrary(libraryName)
            Log.d(TAG, "Loaded: lib$libraryName.so")
            true
        } catch (e: UnsatisfiedLinkError) {
            Log.e(
                TAG,
                "Failed to load lib$libraryName.so — possible 16 KB alignment issue. " +
                "Recompile with: -Wl,-z,max-page-size=16384",
                e
            )
            false
        } catch (e: SecurityException) {
            Log.e(TAG, "Security exception loading lib$libraryName.so", e)
            false
        }
    }
}

Use it in your Activity or Application:

Kotlin
// MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val loaded = NativeLibraryLoader.loadSafely("mynativelib")

        if (!loaded) {
            showCompatibilityError()
        }
    }

    private fun showCompatibilityError() {
        AlertDialog.Builder(this)
            .setTitle("Compatibility Issue")
            .setMessage(
                "A required component couldn't load on this device. " +
                "Try updating the app to get the latest compatibility fixes."
            )
            .setPositiveButton("OK", null)
            .show()
    }
}

This avoids a crash and gives the user a message they can actually act on, instead of a silent ANR.

Detecting page size atĀ runtime

Sometimes you need to know which page size the device is usingā€Šā€”ā€Šfor example, to decide whether to enable a feature backed by a library you haven’t fully audited yet.

Kotlin
// PageSizeDetector.kt

import android.system.Os
import android.system.OsConstants

/**
 * Reads the system page size at runtime using the POSIX sysconf API.
 * Returns 4096 on standard devices, 16384 on 16 KB page-size devices.
 */
object PageSizeDetector {

    fun getPageSizeInBytes(): Long {
        return Os.sysconf(OsConstants._SC_PAGESIZE)
    }

    fun is16KBPageSize(): Boolean {
        return getPageSizeInBytes() == 16384L
    }

    fun description(): String {
        return when (getPageSizeInBytes()) {
            4096L  -> "4 KB"
            16384L -> "16 KB"
            else   -> "${getPageSizeInBytes()} bytes (unknown)"
        }
    }
}

Log it at startupā€Šā€”ā€Šit takes one line and has saved debugging time more than once when a crash report comes in from an unfamiliar device:

Kotlin
// App.kt

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        Log.i("App", "Page size: ${PageSizeDetector.description()}")
    }
}

When you see a crash log from a device you can’t reproduce locally, the page size entry tells you whether you’re looking at an alignment problem or something else entirely.

Auditing bundled native libraries at debugĀ time

This helper scans your app’s native library directory and lists every .so it finds. It won’t tell you the alignment directly (use readelf for that), but it gives you a complete list to work throughā€Šā€”ā€Šwhich matters when you’re auditing a project with a lot of dependencies.

Kotlin
// SdkCompatibilityChecker.kt

import java.io.File

/**
 * Lists all native libraries bundled in the APK at runtime.
 * Run this in debug builds to build your audit list.
 * For actual alignment verification, use readelf on each file.
 */
object SdkCompatibilityChecker {

    private const val TAG = "SdkCompatibilityChecker"

    fun findNativeLibraries(context: android.content.Context): List<String> {
        val nativeLibDir = File(context.applicationInfo.nativeLibraryDir)

        if (!nativeLibDir.exists() || !nativeLibDir.isDirectory) {
            Log.w(TAG, "No native library directory found.")
            return emptyList()
        }

        return nativeLibDir
            .listFiles { file -> file.name.endsWith(".so") }
            ?.map { it.name }
            ?: emptyList()
    }

    fun auditAndLog(context: android.content.Context) {
        val libs = findNativeLibraries(context)

        if (libs.isEmpty()) {
            Log.i(TAG, "No native libraries found.")
            return
        }

        Log.w(TAG, "Found ${libs.size} native libraries — verify each with readelf:")
        libs.forEach { Log.w(TAG, "  -> $it") }
    }
}

Wire it into your Application class behind a BuildConfig.DEBUG check:

Kotlin
class App : Application() {

override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            SdkCompatibilityChecker.auditAndLog(this)
        }
    }
}

Every debug run now logs a full list of native libraries. Paste it into a spreadsheet, mark which ones you own, and track the audit from there.

Test on a 16 KBĀ Emulator

You don’t need a physical device for this. Android Studio ships with 16 KB emulator images.

Create theĀ emulator

  1. Open Android Studio → Device Manager
  2. Click Create Device
  3. Pick a Pixel 8 or later hardware profile
  4. On the system image screen, select an image labelled ā€œ16k page sizeā€ (available for API 35 and API 36)
  5. Finish the setup and start the emulator

Confirm it’s configured correctly

adb shell getconf PAGE_SIZE

16384 means you’re on a 16 KB device. 4096 means something went wrong with the AVD setup.

What to watch for when running yourĀ app

  • Crash on launch → a native library failed to load; check Logcat for the library name
  • UnsatisfiedLinkError in Logcat → that specificĀ .so is 4 KB aligned
  • App runs normally → you’re compliant

Dealing With Third-Party Libraries You Can’t Recompile

Your code might be clean, but one of your dependencies is shipping a 4 KB-aligned .so that you have no control over.

Option 1ā€Šā€”ā€ŠContact the vendor. File a GitHub issue or support ticket referencing the Android 16 KB page size requirement. Most major SDKs (Firebase, Google Play Services, Crashlytics) are already compliant. Smaller or older SDKs may need a nudge.

Option 2ā€Šā€”ā€ŠGate the feature at runtime. While you wait for the vendor to ship a fix, use PageSizeDetector to disable the feature on affected devices:

Kotlin
// FeatureManager.kt

object FeatureManager {

    /**
     * Returns false on 16 KB page-size devices if the underlying
     * native library hasn't been verified as compliant yet.
     * Flip this to true once your SDK vendor ships a fix.
     */
    fun isNativeFeatureEnabled(): Boolean {
        if (PageSizeDetector.is16KBPageSize()) {
            Log.w("FeatureManager", "Skipping native feature on 16 KB device — awaiting SDK update.")
            return false
        }
        return true
    }
}

Option 3ā€Šā€”ā€ŠWrite a Kotlin fallback. For features where a fallback is feasible, have two paths: the native implementation for standard devices, and a pure Kotlin path for 16 KB devices until the library is updated.

Kotlin
// ImageProcessor.kt

class ImageProcessor {

    /**
     * Uses the fast native path on verified devices, falls back to
     * Kotlin on 16 KB page-size devices until the native library is updated.
     */
    fun processImage(bitmap: android.graphics.Bitmap): android.graphics.Bitmap {
        return if (FeatureManager.isNativeFeatureEnabled()) {
            processImageNative(bitmap)  // C++ via JNI
        } else {
            processImageKotlin(bitmap)  // Pure Kotlin fallback
        }
    }

    private external fun processImageNative(
        bitmap: android.graphics.Bitmap
    ): android.graphics.Bitmap

    private fun processImageKotlin(
        bitmap: android.graphics.Bitmap
    ): android.graphics.Bitmap {
        val copy = bitmap.copy(bitmap.config, true)
        // apply transformations
        return copy
    }
}

This keeps the app working on all devices. The Kotlin path is slower, but it beats a crash.

Google Play Requirements

Play Console already flags 4 KB-aligned libraries as warnings when you target API 34 or lower. However, for API 35 (Android 15) and above, 16 KB compliance is now mandatory for all new apps and updates. While Google initially allowed extensions, as of 2026, non-compliant apps with native code will face immediate rejection during the upload process.

Check Play Console → Release → App bundle explorer → [Select Version] → Supported page sizes for any warnings or ā€œNot Supportedā€ labels regarding native library alignment. Deal with them immediately to ensure your releases are not blocked.

Real Performance Numbers

The gains are genuine but not uniform across all app types:

Metric4 KB Pages16 KB Pages
Cold app launchBaselineUp to 30% faster
TLB miss rateHigherLower
Kernel page table sizeLargerSmaller
Memory fragmentationMoreLess
App RAM footprintBaselineMarginally higher

The trade-off: small allocations get rounded up to the next 16 KB boundary, so there’s a slight increase in memory usage. For most apps it’s a few hundred KB at most — well worth the startup speed improvement.

Migration Checklist

Kotlin
Android 16 KB Page Size - Pre-ship Checklist
=============================================

ā–” Unzipped APK and located all .so files under lib/arm64-v8a/
ā–” Ran readelf -l on each .so - confirmed LOAD alignment is 0x4000
ā–” Added -Wl,-z,max-page-size=16384 to CMakeLists.txt or Android.mk
ā–” Rebuilt native libraries - re-verified alignment with readelf
ā–” Audited all third-party .so files - opened tickets with non-compliant vendors
ā–” Added NativeLibraryLoader with UnsatisfiedLinkError handling
ā–” Added PageSizeDetector and logging to Application.onCreate()
ā–” Added SdkCompatibilityChecker to debug builds
ā–” Created a 16k page size AVD in Android Studio
ā–” Ran the app on the 16k emulator - no crashes, no UnsatisfiedLinkError
ā–” Ran ./gradlew lint - no PageSizeAlignment warnings
ā–” Checked Play Console - no native library alignment warnings

FAQ

Does this affect all Android devices right now?

No. The 16 KB page size requires specific kernel and hardware support. Older devices will keep using 4 KB pages. But as Pixel 9 and later devices ship with 16 KB kernels, the affected install base will grow steadily.

My app is pure Kotlin with no NDK. Do I need to do anything?

Probably not. ART handles alignment for managed code automatically. Just double-check your Gradle dependencies for any SDKs that bundle .so filesā€Šā€”ā€Šthose are the only risk for a pure Kotlin app.

Will a 4 KB-aligned .so actually crash the app?

Yes. On a 16 KB page-size device, System.loadLibrary() will throw UnsatisfiedLinkError if the .so isn’t properly aligned. That’s an app crash unless you catch it.

Can one .so file work on both 4 KB and 16 KB devices?

Yes. A library compiled with -Wl,-z,max-page-size=16384 works fine on 4 KB devicesā€Šā€”ā€Šthe extra alignment is just padding that gets ignored. You don’t need separate builds for different page sizes.

What about Unity?

Unity generates native .so files, so yes, it’s affected. Unity has been shipping fixes in recent LTS versions. Make sure you’re on an up-to-date Unity LTS release and rebuild your project after upgrading.

Conclusion

The Android 16 KB page size change is the kind of requirement that’s easy to ignore until it starts causing crashes on new hardware. The fix is straightforward if you own your native codeā€Šā€”ā€Šit’s one linker flag and a rebuild. The harder work is tracking down third-party SDKs that haven’t updated yet and building a plan for those.

Start by running the readelf check on your APK today. If everything comes back as 0x4000, you’re done. If not, the checklist above has every step you need.

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!