Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e59f2a2c authored by Shai Barack's avatar Shai Barack
Browse files

Introduce JniStringCache to cache jstring instances.

This change adds a native cache of jstrings created from native strings.
The goal is to reduce duplicate java.lang.String instances.
Demonstrate usage, and test correctness, by integrating with Parcel.
On background trim (sent just before transition GC prior to freeze), clear the cache to maximize potential GC returns.

Running this on my local device, producing stats from dumpsys meminfo, and processing them, I get:
https://docs.google.com/spreadsheets/d/1tVvMROAOhgphJNuFZoavNh7jG0iTErUHYP2DnQnascE/edit?usp=sharing
Most processes have a >80% hit rate.

Flag: android.os.parcel_string_cache_enabled
Bug: 442140362

Change-Id: I818ad94dd90d58576f5f86bc8a5c6d67dd5ec61a
parent 7c077e50
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -246,6 +246,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.DebugStore;
import com.android.internal.os.JniStringCache;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SafeZipPathValidatorCallback;
import com.android.internal.os.SomeArgs;
@@ -1948,6 +1949,9 @@ public final class ActivityThread extends ClientTransactionHandler
            if (dumpAllocatorStats) {
                Debug.logAllocatorStats();
            }

            pw.println(" ");
            JniStringCache.dump(pw);
        }

        @NeverCompile
@@ -7686,6 +7690,10 @@ public final class ActivityThread extends ClientTransactionHandler
        }
        if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            JniStringCache.clear();
        }

        try {
            if (skipBgMemTrimOnFgApp()
                    && mLastProcessState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+7 −0
Original line number Diff line number Diff line
@@ -370,6 +370,13 @@ flag {
    bug: "345802719"
}

flag {
    name: "parcel_string_cache_enabled"
    namespace: "system_performance"
    description: "Enable JNIStringCache in Parcel"
    bug: "442140362"
}

flag {
    name: "remove_app_profiler_pss_collection"
    is_exported: true
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.os;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;

/** Java companion to native JNIStringCache class. */
public final class JniStringCache {

    /** Dumps cache statistics to the given writer. */
    @NeverCompile
    public static void dump(PrintWriter pw) {
        if (!android.os.Flags.parcelStringCacheEnabled()) {
            return;
        }

        pw.println("JniStringCache");
        pw.format(
                "                Hits: %,10d    Misses: %,10d\n",
                nativeHits(), nativeMisses());
        pw.format(
                "           Evictions: %,10d     Skips: %,10d\n",
                nativeEvictions(), nativeSkips());
        pw.flush();
        pw.println(" ");
    }

    /** Clears the cache. */
    public static void clear() {
        if (!android.os.Flags.parcelStringCacheEnabled()) {
            return;
        }

        nativeClear();
    }

    @CriticalNative
    private static native long nativeHits();

    @CriticalNative
    private static native long nativeMisses();

    @CriticalNative
    private static native long nativeEvictions();

    @CriticalNative
    private static native long nativeSkips();

    @CriticalNative
    private static native void nativeClear();
}
+3 −0
Original line number Diff line number Diff line
@@ -22,3 +22,6 @@ per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS

# Memcg memory accounting
per-file MemcgProcMemoryUtil.java = file:/PERFORMANCE_OWNERS

# JniStringCache
per-file *JniStringCache* = file:/PERFORMANCE_OWNERS
+1 −0
Original line number Diff line number Diff line
@@ -258,6 +258,7 @@ cc_library_shared_for_libandroid_runtime {
                "com_android_internal_os_ClassLoaderFactory.cpp",
                "com_android_internal_os_DebugStore.cpp",
                "com_android_internal_os_FuseAppLoop.cpp",
                "com_android_internal_os_JniStringCache.cpp",
                "com_android_internal_os_KernelAllocationStats.cpp",
                "com_android_internal_os_KernelCpuBpfTracking.cpp",
                "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp",
Loading