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

Commit a74111f8 authored by Ryan Mitchell's avatar Ryan Mitchell
Browse files

Use a ReferenceQueue to prune weak references

Currently ResourcesManager iterates over all of its weak references to
Resources objects and calls Reference#get to determine if the object
has been garbage collected. This method of pruning WeakReferences
causes reference locks to be acquired in the native layer and causes
lock contention when "references are being processed" by the garbage
collector.

This change uses a ReferenceQueue to determine if a reference has been
garbage collected which should improve performance of
ResourcesManager#getResources when the system is under memory pressure.

By only removing garbage collected references when creating new
Resources objects, the lists grow larger and are periodically pruned
rather than attempting to prune entries from the list every getResources
invocation.

Bug: 157575833
Test: used debugger to observe pruning happening correctly
hange-Id: I3277e80edfa441d24de165e738d33c4fac6b4121
Change-Id: I3277e80edfa441d24de165e738d33c4fac6b4121
parent 57e977a5
Loading
Loading
Loading
Loading
+25 −28
Original line number Original line Diff line number Diff line
@@ -52,10 +52,13 @@ import com.android.internal.util.IndentingPrintWriter;


import java.io.IOException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.WeakHashMap;
@@ -69,12 +72,6 @@ public class ResourcesManager {


    private static ResourcesManager sResourcesManager;
    private static ResourcesManager sResourcesManager;


    /**
     * Predicate that returns true if a WeakReference is gc'ed.
     */
    private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
            weakRef -> weakRef == null || weakRef.get() == null;

    /**
    /**
     * The global compatibility settings.
     * The global compatibility settings.
     */
     */
@@ -100,6 +97,7 @@ public class ResourcesManager {
     */
     */
    @UnsupportedAppUsage
    @UnsupportedAppUsage
    private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
    private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
    private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();


    private static class ApkKey {
    private static class ApkKey {
        public final String path;
        public final String path;
@@ -155,6 +153,7 @@ public class ResourcesManager {
        }
        }
        public final Configuration overrideConfig = new Configuration();
        public final Configuration overrideConfig = new Configuration();
        public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
        public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
        final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>();
    }
    }


    /**
    /**
@@ -667,12 +666,15 @@ public class ResourcesManager {
            @NonNull CompatibilityInfo compatInfo) {
            @NonNull CompatibilityInfo compatInfo) {
        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                activityToken);
                activityToken);
        cleanupReferences(activityResources.activityResources,
                activityResources.activityResourcesQueue);


        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
                : new Resources(classLoader);
        resources.setImpl(impl);
        resources.setImpl(impl);
        resources.setCallbacks(mUpdateCallbacks);
        resources.setCallbacks(mUpdateCallbacks);
        activityResources.activityResources.add(new WeakReference<>(resources));
        activityResources.activityResources.add(
                new WeakReference<>(resources, activityResources.activityResourcesQueue));
        if (DEBUG) {
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
@@ -682,11 +684,13 @@ public class ResourcesManager {


    private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
    private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
            @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
        cleanupReferences(mResourceReferences, mResourcesReferencesQueue);

        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
                : new Resources(classLoader);
        resources.setImpl(impl);
        resources.setImpl(impl);
        resources.setCallbacks(mUpdateCallbacks);
        resources.setCallbacks(mUpdateCallbacks);
        mResourceReferences.add(new WeakReference<>(resources));
        mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
        if (DEBUG) {
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
@@ -752,7 +756,6 @@ public class ResourcesManager {
            updateResourcesForActivity(token, overrideConfig, displayId,
            updateResourcesForActivity(token, overrideConfig, displayId,
                    false /* movedToDifferentDisplay */);
                    false /* movedToDifferentDisplay */);


            cleanupReferences(token);
            rebaseKeyForActivity(token, key);
            rebaseKeyForActivity(token, key);


            synchronized (this) {
            synchronized (this) {
@@ -778,10 +781,6 @@ public class ResourcesManager {
            final ActivityResources activityResources =
            final ActivityResources activityResources =
                    getOrCreateActivityResourcesStructLocked(activityToken);
                    getOrCreateActivityResourcesStructLocked(activityToken);


            // Clean up any dead references so they don't pile up.
            ArrayUtils.unstableRemoveIf(activityResources.activityResources,
                    sEmptyReferencePredicate);

            // Rebase the key's override config on top of the Activity's base override.
            // Rebase the key's override config on top of the Activity's base override.
            if (key.hasOverrideConfiguration()
            if (key.hasOverrideConfiguration()
                    && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
                    && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
@@ -794,21 +793,21 @@ public class ResourcesManager {


    /**
    /**
     * Check WeakReferences and remove any dead references so they don't pile up.
     * Check WeakReferences and remove any dead references so they don't pile up.
     * @param activityToken optional token to clean up Activity resources
     */
     */
    private void cleanupReferences(IBinder activityToken) {
    private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references,
        synchronized (this) {
            ReferenceQueue<T> referenceQueue) {
            if (activityToken != null) {
        Reference<? extends T> enduedRef = referenceQueue.poll();
                ActivityResources activityResources = mActivityResourceReferences.get(
        if (enduedRef == null) {
                        activityToken);
            return;
                if (activityResources != null) {
                    ArrayUtils.unstableRemoveIf(activityResources.activityResources,
                            sEmptyReferencePredicate);
                }
            } else {
                ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
        }
        }

        final HashSet<Reference<? extends T>> deadReferences = new HashSet<>();
        for (; enduedRef != null; enduedRef = referenceQueue.poll()) {
            deadReferences.add(enduedRef);
        }
        }

        ArrayUtils.unstableRemoveIf(references,
                (ref) -> ref == null || deadReferences.contains(ref));
    }
    }


    /**
    /**
@@ -896,8 +895,6 @@ public class ResourcesManager {
                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();


            cleanupReferences(activityToken);

            if (activityToken != null) {
            if (activityToken != null) {
                rebaseKeyForActivity(activityToken, key);
                rebaseKeyForActivity(activityToken, key);
            }
            }