Memory leak

Time:2022-4-20

Memory leak refers to the heap memory that has been dynamically allocated in the program. For some reason, the program does not release or cannot release, resulting in a waste of system memory, slowing down the running speed of the program and even system crash—— From Baidu Encyclopedia

It is estimated that the place with the most memory leaks is encountered at work and in various interview questions,
It is often heard that memory leakage is caused as follows:

  1. Non static inner class handler
  2. Forget to unregister the broadcast?????
  3. Bitmap usage not recycled
  4. Static variable reference context
  5. HashMap uses a custom object when the key does not override hashcode, equals
  6. Cross process communication
  7. To be added

Let’s talk about the memory leak I understand, and explain it in one sentence: That is, when a thing is used up by you, you no longer need it, and it still exists in memory after GC

Let’s take a look at some examples

1. Non static inner class handler
public Handler mHandler1 = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

    public Handler mHandler2 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            return false;
        }
    });

    public Handler mHandler3 = new MyHandler();

    public class MyHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        findViewById(R.id.button).setOnClickListener(v -> {
            Toast Maketext (this, "you clicked ----", toast. Length_short) show();
            Message obtain = Message.obtain();
            mHandler1.sendMessage(obtain);
            obtain = Message.obtain();
            mHandler2.sendMessage(obtain);
            obtain = Message.obtain();
            mHandler3.sendMessage(obtain);
        });
    }

The above are several methods we often use handler. Will the above three methods cause memory leakage?
The test code is to jump from mainactivity to mainactivity2, click the button, send the handler message, then exit the mainactivity2 interface, go back and forth several times, and then use the profile to grab the dump information in memory to check whether there is a leak
Let’s measure it: use the tool Android studio profiler mat

Memory leak

Screenshot 10.04.47 PM, April 25, 2021 png
Memory leak

What about the agreed memory leak png

The modification code is as follows:

            Message obtain = Message.obtain();
            mHandler1.sendMessageDelayed(obtain, 25000);
            obtain = Message.obtain();
            mHandler2.sendMessageDelayed(obtain, 25000);
            obtain = Message.obtain();
            mHandler3.sendMessageDelayed(obtain, 25000);

Repeat the operation just now. Ouhou, the memory leaks out.

Memory leak

Screenshot 10.27.23 PM, April 25, 2021 png

Memory leak

Screenshot 10.31.42 PM, April 25, 2021 png

You can see the current reference chain as follows:

Activitythread - > messagequeue - > message - > target (here is our handler) - > mainactivity2
At this point, we can find that the handler does not necessarily cause memory leakage. Memory leakage occurs only when the message we send has not been processed.
2. Will forgetting to unregister the broadcast cause?

As for the broadcast of forgetting to unregister, it’s only heard of. Compared with the cost of unregistering, it’s too low. Just write it. Let’s actually test it: start the operation process, launch mainactivity2, and repeat it four times.

public class MainActivity2 extends AppCompatActivity {
    public static final String TAG = "MainActivity2";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        IntentFilter intentFilter1 = new IntentFilter();
        intentFilter1.addAction(ACTION_MEDIA_MOUNTED);
        intentFilter1.addDataScheme("file");
        BroadcastReceiver aaa = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Log.w(MainActivity2.TAG, "AAA");
            }
        };
        registerReceiver(aaa, intentFilter1);
    }
}
Memory leak

image.png

It can be seen that there is no leakage, and Android studio did not detect it.
Go home and use my computer: there are some results

Memory leak

Screenshot 2021-04-27 8.39.56 PM png

Next, use the professional mat to have a look.

  1. Select the heap dump file and save it.
  2. Use the SDK conversion tool to convert the format recognized by mat. SDK / platform tools / hprof conv original file Hprof new file hprof
  3. Use mat to open a new file. Check whether the objects we care about have strong references
    For specific use, please refer to the following links:
    https://www.jianshu.com/p/7207deafd785

Using company computers:

Memory leak

image.png

Home computer:

Memory leak

image.png

What the hell is going on? By viewing the android-30 source code, you can see that the currently registered broadcast and bound service will be cleared when the activity is deregistered
android.app.ActivityThread#handleDestroyActivity
……
android.app.LoadedApk#removeContextRegistrations

public void removeContextRegistrations(Context context,
            String who, String what) {
        final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled();
        synchronized (mReceivers) {
            ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap =
                    mReceivers.remove(context);
            if (rmap != null) {
                for (int i = 0; i < rmap.size(); i++) {
                    LoadedApk.ReceiverDispatcher rd = rmap.valueAt(i);
                    IntentReceiverLeaked leak = new IntentReceiverLeaked(
                            what + " " + who + " has leaked IntentReceiver "
                            + rd.getIntentReceiver() + " that was " +
                            "originally registered here. Are you missing a " +
                            "call to unregisterReceiver()?");
                    leak.setStackTrace(rd.getLocation().getStackTrace());
                    Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
                    if (reportRegistrationLeaks) {
                        StrictMode.onIntentReceiverLeaked(leak);
                    }
                    try {
                        ActivityManager.getService().unregisterReceiver(
                                rd.getIIntentReceiver());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
            mUnregisteredReceivers.remove(context);
        }

        synchronized (mServices) {
            //Slog.i(TAG, "Receiver registrations: " + mReceivers);
            ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap =
                    mServices.remove(context);
            if (smap != null) {
                for (int i = 0; i < smap.size(); i++) {
                    LoadedApk.ServiceDispatcher sd = smap.valueAt(i);
                    ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
                            what + " " + who + " has leaked ServiceConnection "
                            + sd.getServiceConnection() + " that was originally bound here");
                    leak.setStackTrace(sd.getLocation().getStackTrace());
                    Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
                    if (reportRegistrationLeaks) {
                        StrictMode.onServiceConnectionLeaked(leak);
                    }
                    try {
                        ActivityManager.getService().unbindService(
                                sd.getIServiceConnection());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                    sd.doForget();
                }
            }
            mUnboundServices.remove(context);
            //Slog.i(TAG, "Service registrations: " + mServices);
        }
    }
Memory leak

What’s the situation png

Next, find out why it is like this:

// android. App In activitythread#handledestroyactivity, there will be a judgment before cleaning. Through break point debugging, you can see that there is a memory leak. The current C object of this project is not contextimpl but contextthemewrapper
        Context c = r.activity.getBaseContext();
        if (c instanceof ContextImpl) {
            ((ContextImpl) c).scheduleFinalCleanup(
                    r.activity.getClass().getName(), "Activity");
        }

Continue to investigate why this happened

Leaked version:

Memory leak

image.png
Memory leak

image.png
Memory leak

image.png

Undisclosed version:

Memory leak

image.png
Memory leak

image.png

The key reason is

implementation 'androidx.appcompat:appcompat:1.2.0'
//This library will not be leaked in version 1.1.0, and will be leaked in version 1.2.0.
The details of the change are as follows: interested in reading by yourself
https://developer.android.google.cn/jetpack/androidx/releases/appcompat?hl=zh-cn
3. Me and the memory leak story

Before I actually met and solved the memory leak problem, I felt that high-end, often seen on blog, WeChat official account, but never touched, is very strange.
After you have really solved it: is that it?
The questions are as follows:

        Intent intent = new Intent(this, MyService.class);
        ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Toast.makeText(MainActivity2.this, "bind success", Toast.LENGTH_SHORT).show();
                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        ILoginListener.Stub onLoginResult1 = new ILoginListener.Stub() {

            @Override
            public void onLoginResult() throws RemoteException {
                Toast.makeText(MainActivity2.this, "onLoginResult",
                        Toast.LENGTH_SHORT).show();
            }
        };
        try {
            iMyAidlInterface.registerMediaListener(onLoginResult1);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
Memory leak

image.png

Where iloginlistener Stub is a binder object

    public Binder(@Nullable String descriptor)  {
        mObject = getNativeBBinderHolder();
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);

        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Binder> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        mDescriptor = descriptor;
    }
public final void writeStrongBinder(IBinder val) {
        //Call native method
        nativeWriteStrongBinder(mNativePtr, val);
    }

static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jint nativePtr, jobject object)  
{  
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);  
    if (parcel != NULL) {
        //Ibinder for javaObject, the object here is the corresponding Java layer, ibinder, that is, networkcallback
        const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));  
        if (err != NO_ERROR) {  
            signalExceptionForError(env, clazz, err);  
        }  
    }  
} 

sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj)  
{  
    if (obj == NULL) return NULL;  
    //Here, obj is the binder object of the Java layer. Follow the logic below. Finally, call jbh->get to get the IBinder base note of the native level.
    if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) {  
        JavaBBinderHolder* jbh = (JavaBBinderHolder*)env->GetIntField(obj, gBinderOffsets.mObject);  
        return jbh != NULL ? jbh->get(env, obj) : NULL;  
    }  
    if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) {  
        return (IBinder*)env->GetIntField(obj, gBinderProxyOffsets.mObject);  
    }  
    ALOGW("ibinderForJavaObject: %p is not a Binder object", obj);  
    return NULL;  
}

sp<JavaBBinder> get(JNIEnv* env, jobject obj)  
{  
    AutoMutex _l(mLock);  
    sp<JavaBBinder> b = mBinder.promote();  
    if (b == NULL) {  
        b = new JavaBBinder(env, obj);  
        mBinder = b;  
        ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%d\n",  
             b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount());  
    }  

    return b;  
}

JavaBBinder(JNIEnv* env, jobject object)  
    : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))  
    //Here, a global reference is created. If env - > deleteglobalref (object) is not actively called, the Java layer object, that is, networkcallback, will not be released.
{  
    ALOGV("Creating JavaBBinder %p\n", this);  
    android_atomic_inc(&gNumLocalRefs);  
    incRefsCreated(env);  
}

Detailed analysis of cross process memory leakage:
https://blog.csdn.net/skqcsy/article/details/51882049

HashMap example

https://juejin.cn/post/6854573213427433480

antic

Often open Android studio to check the source code. If you find that the jump fails, it will be red, because the full version of SDK has not been downloaded
https://github.com/anggrayudi/android-hidden-api
https://drive.google.com/drive/folders/17oMwQ0xBcSGn159mgbqxcXXEcneUmnph
Download the corresponding version and replace it to enjoy silk slide

Recommended Today

Modul of fastems

Each module of fastems is implemented from the abstract class Fastems.Mms.Client.Infrastructure.UiModuleBase; public class DataManagerModule : UiModuleBase { public override void Initialize() { AddResourceDictionary(“/Resources/DataManagerResources.xaml”, typeof(DataManagerModule)); RegisterViewWithRegion(“DialogRegion”, typeof(DialogView)); RegisterViewWithRegion(“BusyIndicatorRegion”, typeof(BusyIndicatorView)); } } And Fastems.Mms.Client.Infrastructure.UiModuleBase inherits from Fastems.Mms.Client.Infrastructure.ModuleBase public abstract class UiModuleBase : ModuleBase { [Import] public IRegionManager RegionManager { get; set; } [Import] public IMergedDictionaryRegistry MergedDictionaryRegistry { […]