From 1b11aaaf9ba824dc5df363736d54601e908efec6 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Mon, 18 Mar 2019 20:15:28 -0700 Subject: [PATCH] Preload non-bootclasspath Java lib ClassLoaders. This is specifically for HIDL but is applicable to other libs. Classes on the bootclasspath are implicitly used by apps. For this reason, many classes should not go there. However, there are some libraries which are used by many apps/processes which are still nice to preload the ClassLoaders of. Now, cacheNonBootclasspathSystemLibs in ApplciationLoaders keeps a map of jar -> ClassLoader in zygote to be retrieved by child processes. Bug: 128529256 Bug: 127406460 Test: boot Pixel 2, verify libs are preloaded and used, try apps that use these libraries. Test: grep for ClassLoaderContext errors, for instance: - ClassLoaderContext shared library size mismatch - ClassLoaderContext classpath element mismatch Test: showmap on various processes which use the preloaded libs. Change-Id: I351bf1679e9a928c10dca860b6cd6cb414c3bb8e --- core/java/android/app/ApplicationLoaders.java | 151 +++++++++++++++++- core/java/android/app/LoadedApk.java | 2 +- .../com/android/internal/os/ZygoteInit.java | 31 ++++ 3 files changed, 182 insertions(+), 2 deletions(-) diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java index 9ef24c6c2aeb4..faa30f3a98b89 100644 --- a/core/java/android/app/ApplicationLoaders.java +++ b/core/java/android/app/ApplicationLoaders.java @@ -17,20 +17,27 @@ package android.app; import android.annotation.UnsupportedAppUsage; +import android.content.pm.SharedLibraryInfo; import android.os.Build; import android.os.GraphicsEnvironment; import android.os.Trace; import android.util.ArrayMap; +import android.util.Log; import com.android.internal.os.ClassLoaderFactory; import dalvik.system.PathClassLoader; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** @hide */ public class ApplicationLoaders { + private static final String TAG = "ApplicationLoaders"; + @UnsupportedAppUsage public static ApplicationLoaders getDefault() { return gApplicationLoaders; @@ -54,6 +61,26 @@ public class ApplicationLoaders { libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries); } + /** + * Gets a class loader for a shared library. Additional dependent shared libraries are allowed + * to be specified (sharedLibraries). + * + * Additionally, as an optimization, this will return a pre-created ClassLoader if one has + * been cached by createAndCacheNonBootclasspathSystemClassLoaders. + */ + ClassLoader getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targetSdkVersion, + boolean isBundled, String librarySearchPath, String libraryPermittedPath, + ClassLoader parent, String classLoaderName, List sharedLibraries) { + ClassLoader loader = getCachedNonBootclasspathSystemLib(zip, parent, classLoaderName, + sharedLibraries); + if (loader != null) { + return loader; + } + + return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, + librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries); + } + private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String cacheKey, @@ -95,7 +122,9 @@ public class ApplicationLoaders { classloader, librarySearchPath, libraryPermittedPath); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - mLoaders.put(cacheKey, classloader); + if (cacheKey != null) { + mLoaders.put(cacheKey, classloader); + } return classloader; } @@ -107,6 +136,112 @@ public class ApplicationLoaders { } } + /** + * Caches system library class loaders which are not on the bootclasspath but are still used + * by many system apps. + * + * All libraries in the closure of libraries to be loaded must be in libs. A library can + * only depend on libraries that come before it in the list. + */ + public void createAndCacheNonBootclasspathSystemClassLoaders(SharedLibraryInfo[] libs) { + if (mSystemLibsCacheMap != null) { + Log.wtf(TAG, "Already cached."); + return; + } + + mSystemLibsCacheMap = new HashMap(); + + for (SharedLibraryInfo lib : libs) { + createAndCacheNonBootclasspathSystemClassLoader(lib); + } + } + + /** + * Caches a single non-bootclasspath class loader. + * + * All of this library's dependencies must have previously been cached. + */ + private void createAndCacheNonBootclasspathSystemClassLoader(SharedLibraryInfo lib) { + String path = lib.getPath(); + List dependencies = lib.getDependencies(); + + // get cached classloaders for dependencies + ArrayList sharedLibraries = null; + if (dependencies != null) { + sharedLibraries = new ArrayList(dependencies.size()); + for (SharedLibraryInfo dependency : dependencies) { + String dependencyPath = dependency.getPath(); + CachedClassLoader cached = mSystemLibsCacheMap.get(dependencyPath); + + if (cached == null) { + Log.e(TAG, "Failed to find dependency " + dependencyPath + + " of cached library " + path); + return; + } + + sharedLibraries.add(cached.loader); + } + } + + // assume cached libraries work with current sdk since they are built-in + ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/, + null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/, + null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/); + + if (classLoader == null) { + Log.e(TAG, "Failed to cache " + path); + return; + } + + CachedClassLoader cached = new CachedClassLoader(); + cached.loader = classLoader; + cached.sharedLibraries = sharedLibraries; + + Log.d(TAG, "Created zygote-cached class loader: " + path); + mSystemLibsCacheMap.put(path, cached); + } + + private static boolean sharedLibrariesEquals(List lhs, List rhs) { + if (lhs == null) { + return rhs == null; + } + + return lhs.equals(rhs); + } + + /** + * Returns lib cached with createAndCacheNonBootclasspathSystemClassLoader. This is called by + * the zygote during caching. + * + * If there is an error or the cache is not available, this returns null. + */ + private ClassLoader getCachedNonBootclasspathSystemLib(String zip, ClassLoader parent, + String classLoaderName, List sharedLibraries) { + if (mSystemLibsCacheMap == null) { + return null; + } + + // we only cache top-level libs with the default class loader + if (parent != null || classLoaderName != null) { + return null; + } + + CachedClassLoader cached = mSystemLibsCacheMap.get(zip); + if (cached == null) { + return null; + } + + // cached must be built and loaded in the same environment + if (!sharedLibrariesEquals(sharedLibraries, cached.sharedLibraries)) { + Log.w(TAG, "Unexpected environment for cached library: (" + sharedLibraries + "|" + + cached.sharedLibraries + ")"); + return null; + } + + Log.d(TAG, "Returning zygote-cached class loader: " + zip); + return cached.loader; + } + /** * Creates a classloader for the WebView APK and places it in the cache of loaders maintained * by this class. This is used in the WebView zygote, where its presence in the cache speeds up @@ -151,4 +286,18 @@ public class ApplicationLoaders { private final ArrayMap mLoaders = new ArrayMap<>(); private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders(); + + private static class CachedClassLoader { + ClassLoader loader; + + /** + * The shared libraries used when constructing loader for verification. + */ + List sharedLibraries; + } + + /** + * This is a map of zip to associated class loader. + */ + private Map mSystemLibsCacheMap = null; } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 0f1ba390cd7aa..53849ba923393 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -675,7 +675,7 @@ public final class LoadedApk { // Shared libraries get a null parent: this has the side effect of having canonicalized // shared libraries using ApplicationLoaders cache, which is the behavior we want. - return ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(jars, + return ApplicationLoaders.getDefault().getSharedLibraryClassLoaderWithSharedLibraries(jars, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, /* parent */ null, /* classLoaderName */ null, sharedLibraries); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index c6303518d9ae0..d695ad6646f87 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -21,6 +21,8 @@ import static android.system.OsConstants.S_IRWXO; import android.content.res.Resources; import android.content.res.TypedArray; +import android.app.ApplicationLoaders; +import android.content.pm.SharedLibraryInfo; import android.os.Build; import android.os.Environment; import android.os.IInstalld; @@ -139,6 +141,9 @@ public class ZygoteInit { bootTimingsTraceLog.traceBegin("PreloadClasses"); preloadClasses(); bootTimingsTraceLog.traceEnd(); // PreloadClasses + bootTimingsTraceLog.traceBegin("CacheNonBootClasspathClassLoaders"); + cacheNonBootClasspathClassLoaders(); + bootTimingsTraceLog.traceEnd(); // CacheNonBootClasspathClassLoaders bootTimingsTraceLog.traceBegin("PreloadResources"); preloadResources(); bootTimingsTraceLog.traceEnd(); // PreloadResources @@ -344,6 +349,32 @@ public class ZygoteInit { } } + /** + * Load in things which are used by many apps but which cannot be put in the boot + * classpath. + */ + private static void cacheNonBootClasspathClassLoaders() { + // These libraries used to be part of the bootclasspath, but had to be removed. + // Old system applications still get them for backwards compatibility reasons, + // so they are cached here in order to preserve performance characteristics. + SharedLibraryInfo hidlBase = new SharedLibraryInfo( + "/system/framework/android.hidl.base-V1.0-java.jar", null /*packageName*/, + null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN, + null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/); + SharedLibraryInfo hidlManager = new SharedLibraryInfo( + "/system/framework/android.hidl.manager-V1.0-java.jar", null /*packageName*/, + null /*codePaths*/, null /*name*/, 0 /*version*/, SharedLibraryInfo.TYPE_BUILTIN, + null /*declaringPackage*/, null /*dependentPackages*/, null /*dependencies*/); + hidlManager.addDependency(hidlBase); + + ApplicationLoaders.getDefault().createAndCacheNonBootclasspathSystemClassLoaders( + new SharedLibraryInfo[]{ + // ordered dependencies first + hidlBase, + hidlManager, + }); + } + /** * Load in commonly used resources, so they can be shared across processes. *