Learn your have doubt, small doubt is small, big doubt is big into.

What is a memory leak?

Memory leak: Useless objects continue to occupy the memory or the memory of useless objects cannot be released in a timely manner. (After a program allocates memory space, the memory is not released after it is used. The result is that the memory cell is occupied and cannot be used again until the program ends. Impact of Memory leak: The application is prone to Memory overflow, that is, OOM (Out of Memory). Memory overflow: in OOM, the application requests more memory than the system can provide. Impact of memory overflow: ANR and even Crash are caused. Memory jitter: Memory jitter refers to the phenomenon that a large number of objects are created or recycled in a short time. It is mainly the creation and recycling of a large number of objects in the cycle, so that the UI thread is frequently blocked, resulting in the picture stuck. Their importance levels are memory overflow > Memory leak > memory jitter.

Root cause of a memory leak

An object that was supposed to be reclaimed, for some reason (a strong reference to the object was held by another object that was in use, and was not freed in time), thus causing memory cells to remain occupied, causing memory leaks, and possibly even memory overflow! In short: the life cycle of the holder > the life cycle of the referenced. Basic knowledge of the principle: About the JVM memory partitioning and GC mechanism (please search for yourself, I will write a separate article on this later)

Memory leak classification in Android

(1) The thread is not finished.

  • The life cycle of the main thread’s Looper object = the life cycle of the application
  • In Java, both non-static inner classes and anonymous inner classes hold references to external classes by default
  • Reference relationships: unprocessed/processed messages -> Handler instances -> external classes
  • Life cycle of Handler > life cycle of external class. When an external class is destroyed, the external class cannot be collected by the garbage collector (GC).
  • Solution :(1) static inner class + weak reference; (2) Empty the message queue in Handler when the external class ends its life cycle.

(2) non-static inner class

  • Because non-static inner classes hold implicit references to external classes, they are prone to unexpected leaks. Let’s say we create an inner class that holds a reference to a static variable.
  • Solution :(1) use static inner classes (static inner classes do not hold references to external classes, breaking chained references); (2) Pay attention to the life cycle of managing references; (3) Avoid static variables.

(3) singleton mode

  • The singleton nature causes it to be as long as the application life cycle. Leaks usually occur when an Activity Context is passed in.
  • Solution: You need to use the Context. Do not pass in the Activity Context. Instead, use the Application Context.

Static variable reference

  • Since the life cycle of a static variable starts when the class is loaded and ends when the class is unloaded, that is, the static variable is released when the application process dies. If an Activity/View is referenced in a static variable, then the Activity is referenced, As with the lifetime of static variables, they remain unreleased, causing a memory leak.
  • Solution :(1) use Application Context; (2) weak reference; (3) At the end of the life cycle, disconnect the reference chain.

(5) memory leakage caused by objects in the collection not being cleaned

  • When we don’t need the object, we don’t clean its references out of the collection, so the collection gets bigger and bigger. The situation is even worse if the collection is static.
  • Solution: Before the Activity exits, clear the collection, set it to NULL, and exit the program.

(6) BitMap occupies too much memory

  • Bitmap parsing takes up memory, but the memory only provides 8M space for bitmap. If there are too many images and the bitmap is not recycled in time, the memory will overflow.
  • Solution :(1) recycle the memory and set it to null; (2) Load images after compression; (3) When you need to use a lot of bitmaps, try to cache them in arrays for reuse.

(7) Attribute animation

  • The animation holds the View object, which in turn holds the Activity
  • Solution :(1) disable the animation when the page exits destroy; (2) If you jump from the current page to another page, you should also pause the animation in onPause to avoid wasting resources, and resume the animation in onResume when you return to the page.

(8) Resources are not closed in time

  • Resource objects such as cursors, streams, broadcastReceivers, etc., use buffers. Close () methods should be closed when they are not used or when they are finished, so that their buffers are recycled.
  • Solution: Close in time.

(9) Subscribe/unsubscribe mismatch (e.g. Service listening/broadcast unregister)

  • If the Service registration listening is not cancelled, the Service will hold the page reference consistently, resulting in a memory leak.
  • The BroadcastReceiver is not only referenced by the Activity, but also referenced by system services such as AMS and managers. As a result, the BroadcastReceiver cannot be collected, and it contains a reference to the Activity. Context in the onReceive method), which causes the Activity to fail to be recycled (just because the Activity calls back to onDestroy does not mean the Activity was reclaimed), causing a serious memory leak.
  • Solution: Unsubscribe in time.

(10) task not in time to cancel (Service/WebView/Retrofit/Rxjava, etc.)

  • If the task is not closed, there will certainly be a memory leak, and even worse, if some resetting work is done during the normal destruction process, there may be a null pointer and other exceptions when the task continues to report work.
  • Solution: Cancel the task immediately, follow the host’s life cycle, don’t let it live…

(11) Create objects in onMeasure/onDraw with high execution frequency

  • Create objects in the body of the loop, customize the View’s onDraw() to create objects, initialize lots of Bitmaps, and create large memory objects frequently.
  • Solution :(1) try to avoid creating objects inside the loop. Instead, move object creation outside the loop. (2) Custom View’s onDraw() method will be called frequently, in which the object should not be created frequently; (3) For objects that can be reused, object pools can be used to cache them.

(12) Toast

  • The Context is used as the initialization parameter of the LinearLayout inside the Toast. It will always hold the Activity. The Toast display is time-limited (asynchronous task). Since the Toast display does not end the lifecycle without ending, the Activity leaks memory at this point.
  • Solution :(1) instead of ToastUtil, wrap a ToastUtil and call it using ApplicationContext (or getApplicationContext); (2) There is also a way to cancel the display by calling the TOAST variable Cancel

Take a nap and start

Why memory profiler?

The Memory Profiler is a component in the Android Profiler that helps you identify Memory leaks and jitter that can cause your application to stall, freeze, or even crash. It displays a real-time chart of application memory usage, allowing you to capture heap dumps, enforce garbage collection, and track memory allocation. Memory Profiler has been added to automatically detect Memory leaks in activities and fragments. Using this feature is very simple.

Field experience

(1) Practical operation environment

  • Optional, use your own environment and code as well
  • SamplePop code download
  • The SamplePop environment is as follows:

Android Studio 4.0 Gradle Version 6.1.1 Android API Version 30

(2) Open the path

  • View -> Tool Windows -> Profiler
  • You can also Run the program directly and enable profiler monitoring: Run -> profiler

(3) Tool preview

(4) Details page

Window details:

  • Window 1: It is composed of the following function buttons

Performance category switch button, including CPU, Memeory, Network, and Energy. Button used to enforce garbage collection events used to capture heap dumps] button used to specify how often the parser captures memory allocations dropdown menu: Full: Captures all object allocations in memory Sampled periodically Object allocations in memory None: Stops tracking application memory allocations

  • Window 2: set of page adjustment buttons, including: Shrink, enlarge, reset, pause, start, etc.
  • Window 3: Event timeline, which displays different states of the Activity lifecycle, user interaction events, such as click, rotation, and so on.
  • Window 4: Memory count legend:

The memory Stack used by the buffer queue to display pixels (including GL surfaces, GL textures, and so on) to the screen: Allocated memory of the native stack and Java stack in your application Code: the memory your application is using to process Code and resources such as dex bytecode, DEX Code,.so library and font Others: The memory your application is using that is not sure how to sort. The number of Java/Kotlin objects allocated by your application. This number does not count toward objects allocated in C or C++

  • Window 5: Memory usage timeline, which displays the following:

1. A stacked chart showing how much memory each memory category is currently using, as shown on the Y-axis on the left and the color keys at the top 2. A dashed line representing the number of objects allocated, as shown on the Y-axis on the right. 3. Icon for each garbage collection event.

(5) Check memory allocation

Drag on the timeline to select which zone allocations you want to view

Description of each window:

  • 1. Selection range: You can drag to select the part of data you want to analyze
  • 2. Select the heap to check:

Image Heap: A system boot image that contains classes preloaded during boot. Zygote heap: a copy-on-write heap in which the application process is derived from the Android system app heap: the main heap in which your application allocates memory JNI Heap: Shows where Java native interface (JNI) references are allocated and released to the heap

  • 3. Choose how to arrange distribution:

Arrange by class: Groups all assignments by class name. Arrange by package: group all assignments by package name; callstack: group all assignments to their corresponding callstack

  • 4. Filters: Search filtering, matchCase is case-sensitive, and Regex uses regular expressions
  • 5. Class name window, click to enter Instance View
  • 6. In the instance window, click on the Call Stack (thread assigned by the instance).
  • 7. In the thread window to show where and in which thread the instance is assigned, right-click -> Jump to Source

From the image above, you can view the following information about object allocation:

  • What types of objects are allocated and how much space they use.
  • Each allocated stack trace is included in which thread.
  • When an object is unallocated.

(6) Capture heap dumps

Window description:

  • 1. Execute heap dump: after performing some operations on the MemoryProfilerActivity page, I return to MainActivity, wait for 1s, and execute heap dump.
  • 2. Page leak filtering: Filters the analysis data of Activity and Fragment instances that have memory leaks. The data types displayed in the filter include:

A Fragment instance that has no valid FragmentManager but is still referenced

  • 3. Item category filtering: only item categories are displayed
  • 4. Class window details:

Allocations: Allocations in the heap Native Size: The total amount of native memory (in bytes) used by this object type, which you’ll see here for some objects allocated by Java because Android uses its native memory Shallow Size for some framework classes (such as Bitmap) : Retained Size: The total amount of Memory Retained in bytes for all instances of this type. Retained Size: The total amount of memory Retained in bytes for all instances of this type.

  • 5. Instance window details:

Depth: the shortest hop count from any GC root to the selected instance Native Size: Shallow Size: the Size of the Retained instance in Java memory: the Size of the Retained memory

  • 6. Instance reference: To display each reference of the corresponding Instance object, right-click -> Jump to Source/Go to Instance

After you capture the heap dump, you can view the following information:

  • What types of objects are assigned to your application, and how many of each.
  • How much memory each object is currently using.
  • Where in the code does a reference to each object remain?
  • The call stack to which the object is assigned

(7) Sample code

Handler memory leak drill, first look at the code:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate: "); setContentView(R.layout.activity_memory_profiler); mResultTv = findViewById(R.id.memory_type_result); ((RadioGroup) findViewById(R.id.memory_handler_type)). setOnCheckedChangeListener(mOnCheckedChangeListener); } private RadioGroup.OnCheckedChangeListener mOnCheckedChangeListener = new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { switch (i) { case R.id.handler_no_static: memoryTestType = MEMORY_ERROR_NO_STATIC; break; case R.id.handler_inner_class: memoryTestType = MEMORY_ERROR_INNER_CLASS; break; case R.id.handler_static_weak: memoryTestType = MEMORY_NORMAL_STATIC_WEAK; break; case R.id.handler_no_static_clear_message: memoryTestType = MEMORY_NORMAL_MESSAGE_CLEAR; break; default: break; }}}; public void onMemoryHandlerMonitor(View view) { Log.d(TAG, "onMemoryHandlerMonitor: "); monitorHandler(); } private static final int MESSAGE_DELAY = 5 * 1000; private static final int MEMORY_ERROR_NO_STATIC = 1; private static final int MEMORY_ERROR_INNER_CLASS = 2; private static final int MEMORY_NORMAL_STATIC_WEAK = 3; private static final int MEMORY_NORMAL_MESSAGE_CLEAR = 4; Private int memoryTestType = MEMORY_ERROR_NO_STATIC; private int memoryTestType = MEMORY_ERROR_NO_STATIC; TextView mResultTv; private Handler mHandler; @Override protected void onDestroy() { Log.d(TAG, "onDestroy: "); if (MEMORY_NORMAL_MESSAGE_CLEAR == memoryTestType) { mHandler.removeCallbacksAndMessages(null); } super.onDestroy(); } // Private void monitorHandler() {log.d (TAG, "monitorHandler: "); Switch (memoryTestType) {case MEMORY_ERROR_NO_STATIC: // Leakage 1: use no-static inner class mHandler = new NoStaticHandler(); break; case MEMORY_ERROR_INNER_CLASS: // Leak 2: MHandler = new Handler() {@override public void handleMessage(@nonnull Message MSG) {log.d (TAG, "inner class handler handleMessage: " + msg.what); mResultTv.setText("MEMORY_ERROR_INNER_CLASS"); }}; break; case MEMORY_NORMAL_STATIC_WEAK: MHandler = new StaticHandler(this); mHandler = new StaticHandler(this); break; MHandler = new NoStaticHandler(); case MEMORY_NORMAL_MESSAGE_CLEAR: // Does not disclose information. break; default: Log.e(TAG, "monitorHandler: default no handle"); return; } / / perform event queue delay sending mHandler. SendEmptyMessageDelayed (1, MESSAGE_DELAY); } //no-static inner class: Class NoStaticHandler extends Handler {@override public void handleMessage(Message MSG) {log.d (TAG, "NoStaticHandler handleMessage: " + msg.what); mResultTv.setText(memoryTestType == MEMORY_ERROR_NO_STATIC ? "MEMORY_ERROR_NO_STATIC" : "MEMORY_NORMAL_MESSAGE_CLEAR"); } } private static class StaticHandler extends Handler { private WeakReference<Activity> reference; public StaticHandler(Activity activity) { super(activity.getMainLooper()); reference = new WeakReference<>(activity); } @Override public void handleMessage(@NonNull Message msg) { Log.d(TAG, "StaticHandler handleMessage: " + msg.what); Activity activity = reference.get(); if (null ! = activity) { Log.d(TAG, "StaticHandler handleMessage: ui update"); ((TextView) activity.findViewById(R.id.memory_type_result)). setText("MEMORY_NORMAL_STATIC_WEAK"); }}}Copy the code

The MemoryProfilerActivity interface is as follows:

The heap dump test is a non-static internal class of Handler, and the process is as follows: 1. Enter the main screen MainActivity, click to enter MemoryProfilerActivity 2. Select Handler uses no-static inner classes and click Simulate Handler Leaks 3. Click [back] to return to MainActivity, wait 1 second, click [heap dump button] 4 of AS (AndroidStudio). Generate a heap dump and prepare for the following analysis

Leaks: MemoryProfilerActivity Leaks: MemoryProfilerActivity Leaks: MemoryProfilerActivity Leaks: MemoryProfilerActivity Leaks 2. In the Class name window, click on the leaked Actvity class. In the Instance View, it is found that NoStaticHandler holds a reference to the MemoryProfilerActivity. As a result, MemoryProfilerActivity does not meet GC criteria when performing heap dumps. 3.AS (Activity/Fragment Leaks) refers to whether a GC condition is met, not whether it has already been GC. Here’s an example: (after entering an Activity does not perform any operation), and then quit, this is, of course, will not cause a memory leak, because it satisfy the GC conditions, but also does not mean that it will immediately be GC off, will you be in a heap dump file you can see this instance remains (could exist for a long time), if you try N active GC, and then see a heap dump file, It was dropped by GC. Now let’s optimize it. Let’s go!

(5) Optimization of Handler memory leakage: static inner class + weak reference mode 1. Do this in the same order as in step 3, but select Handler static inner class + Weak reference 2. Check the Activity/Fragment Leaks file to see if the MemoryProfilerActivity has no memory Leaks.

AS this IDE code risk tips do very well, we should pay attention to the special color identification in the development process, and then according to the prompts to optimize it, basically can solve most of the problems. In the SamplePop code, you can get a feel for the special color flags that can cause problems such as memory leaks:

(7) After class exercises (given by Oli)

// Simulate subscribed/unsubscribed missing leak @SuppressLint("MissingPermission") private void monitorRegister() {log.d (TAG, "monitorRegister: "); LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, TimeUnit.MINUTES.toMillis(5), 100, new LocationListener() { @Override public void onLocationChanged(@NonNull Location location) { Log.d(TAG, "onLocationChanged: "); }}); } private static Object inner; private void monitorNoStaticInnerClass() { Log.d(TAG, "monitorNoStaticInnerClass: "); class InnerClass { } inner = new InnerClass(); }Copy the code

Xiaobian extension links

  • SamplePop code download
  • Android Performance Optimization family Bucket

Refer to the link

  • This is the first article that should be read
  • Google Developer -> This is the best article on deep heap dump instance size
  • Good blog post -> This is a very refined look at memory leaks

The only thing to live up to is the beauty

❤ ❤ than heart