Android memory leak detection and resolution

1. Background:

1.1, what is a memory leak

A memory leak means that after a program applies for memory, it cannot release the requested memory space, which causes the system to fail to reclaim the memory in time and allocate it to other processes.

1.2, memory management

49f973de611c49b99b6d44b725df01b3.png

1.3, garbage collection

 It can be seen from the above that the main object recovered by GC is the java heap, that is, the new object. garbage collection algorithm

 

 mark-sweep algorithm

Thought:

Marking phase: mark all objects that need to be recycled; 

Clearing phase: unified clearing (recycling) of all marked objects

advantage:

easy to implement

shortcoming:

 Efficiency problem: the two processes of marking and clearing are not efficient

Space problem: After mark-sweep, a large number of discontinuous memory fragments will be generated

Scenes:

Low object survival rate & low frequency of garbage collection activities (such as old generation)

copy algorithm

Thought:

Divide the memory into two pieces of equal size, each time one of them is used, when the used piece of memory runs out, copy the surviving objects in this piece of memory to another piece of untried memory that will eventually be used That piece of memory is cleaned up at once.

advantage:

Solved the problem of low cleaning efficiency in the mark-sweep algorithm: only half of the memory area is reclaimed each time

Solve the problem of discontinuous memory fragmentation caused by space in the mark-clear algorithm: move the surviving objects on the used memory to the pointer on the top of the stack, and allocate memory in order

shortcoming:

Each time the memory used is reduced to half of the original

When the object survival rate is high, many copy operations need to be done, that is, the efficiency will become lower

Scenes:

Object survival is low & areas that require frequent garbage collection (such as the new age)》

Tagging - Collation Algorithm

Thought:

Marking phase: mark all objects that need to be recycled;

Finishing phase: move all surviving objects towards one

Cleanup phase: Unified cleanup (recycling) of objects other than the end

advantage:

Solved the problem of low cleaning efficiency in the mark-sweep algorithm: clear the area outside the end at one time

Solved the problem of discontinuous memory fragmentation in space in the mark-clear algorithm: move the used memory in many steps: mark, organize, and clear the surviving objects to the pointer on the top of the stack, and allocate memory in order.

shortcoming:

Many steps: mark, organize, clear

Scenes:

Low object survival rate & low frequency of garbage collection activities (such as old generation)

Generational Collection Algorithm

Thought:

Divide Java heap memory into: new generation & old generation according to different object life cycle

Features of each area:

Young generation: low object survival rate & high frequency of garbage collection

Old Age: Low Object Survival Rate & Garbage Collection Behavior Frequency Anywhere

According to the characteristics of each area, select the corresponding garbage collection algorithm (that is, the algorithm introduced above)

The new generation: using the replication algorithm

Old generation: use mark-clear algorithm, mark-sort algorithm

advantage:

High efficiency and high space utilization: choose different garbage collection algorithms according to the characteristics of different regions

shortcoming:

Multiple heap regions need to be maintained, adding to the complexity. Object promotion may occur, resulting in increased memory pressure in the old generation.

Scenes:

Virtual machines basically use this algorithm

204bbecee1694fb7a81b3c2e49865758.png

1.4. It can be seen from the above that objects that can be recycled will not cause garbage and will not occupy a large amount of memory. Therefore, in object management, we must pay attention to the fact that GC can recycle useless objects in time, otherwise the memory will gradually be filled up, eventually leading to Out of memory, program crashes and ANR exceptions occur.

To sum up, the main reason is that objects with a long life cycle refer to objects with a short life cycle, so that objects with a short life cycle cannot be released and recycled in time.

Common causes of memory leaks:

Singleton hold activity short life cycle

Non-static inner classes will hold references to outer classes

An outer class holds a static object of a non-static inner class

Handler or Runnable as non-static inner class

Resources need to be closed, and memory leaks caused by BroadcastReceiver, ContentObserver, File, Cursor, and Bitmap collection objects not being cleaned up in time

2. Tools for analyzing memory leaks

2.1 leakcanary 

leakcanary is a tool for monitoring android and java memory leaks. He can dynamically collect memory leaks in the program without affecting the normal operation of the program

Github website: https://github.com/square/leakcanary

Use, add dependencies in app's build.gradle

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.10'

Initialize leakcanary in application

LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
                .retainedVisibleThreshold(3)
                .computeRetainedHeapSize(false)
                .build();
LeakCanary.setConfig(config);

 2.2 AndroidStudio's Profiles

5cf2ee2f211e47efbeccff821a3fb1b7.png

 23d25de9d07e40c88d33d396c991424c.png

 Three, examples, remember a memory leak problem

3.1 The mobile phones we usually use may have relatively strong performance, and they will not be used all the time. Generally, memory leaks will not cause app paralysis. However, when encountering the leakage problem of the development board, it will become a fatal error. Due to the small memory and long running time, in case of a memory leak, the memory will be increased until the memory is exhausted and the program crashes.

3.2 The place that caused the above problems recently is a scanning code payment device, which turns on the camera to identify the QR code, and the asynchronous thread scans the QR code and then exits the scanning page. The memory leak that was finally located is caused by the ActivityUtils page management tool class. memory leak of

3.3 Source code causing memory leak:

mCamera.setDisplayOrientation(270);//竖屏显示
mCamera.setPreviewDisplay(mHolder);
mCamera.setPreviewCallback(previewCallback);
mCamera.startPreview();
mCamera.autoFocus(autoFocusCallback);

PreviewCallback previewCallback = new PreviewCallback() {
	public void onPreviewFrame(byte[] data, Camera camera) {
		if (data != null) {
			Camera.Parameters parameters = camera.getParameters();
			Size size = parameters.getPreviewSize();//获取预览分辨率

			//创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的
			Image source = new Image(size.width, size.height, "Y800");
			//图片旋转了90度,将扫描框的TOP作为left裁剪
			source.setData(data);//填充数据

			ArrayList<HashMap<String, String>> result = new ArrayList<>();
			  //解码,返回值为0代表失败,>0表示成功
			int dataResult = mImageScanner.scanImage(source);
			if (dataResult != 0) {
				playBeepSoundAndVibrate();//解码成功播放提示音
				SymbolSet syms = mImageScanner.getResults();//获取解码结果
				for (Symbol sym : syms) {
					HashMap<String, String> temp = new HashMap<>();
					temp.put(ScanConfig.TYPE, sym.getSymbolName());
					temp.put(ScanConfig.VALUE, sym.getResult());
					result.add(temp);
				}
				if (result.size() > 0) {
					finish();
					EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));
				} 
			}
			AsyncDecode asyncDecode = new AsyncDecode();
			asyncDecode.execute(source);//调用异步执行解码
		}
	}
};  
private class AsyncDecode extends AsyncTask<Image, Void, ArrayList<HashMap<String, String>>> {
        @Override
        protected ArrayList<HashMap<String, String>> doInBackground(Image... params) {
            final ArrayList<HashMap<String, String>> result = new ArrayList<>();
            Image src_data = params[0];//获取灰度数据
            //解码,返回值为0代表失败,>0表示成功
            final int data = mImageScanner.scanImage(src_data);
            if (data != 0) {
                playBeepSoundAndVibrate();//解码成功播放提示音
                SymbolSet syms = mImageScanner.getResults();//获取解码结果
                for (Symbol sym : syms) {
                    HashMap<String, String> temp = new HashMap<>();
                    temp.put(ScanConfig.TYPE, sym.getSymbolName());
                    temp.put(ScanConfig.VALUE, sym.getResult());
                    result.add(temp);
                    if (!ScanConfig.IDENTIFY_MORE_CODE) {
                        break;
                    }
                }
            }
            return result;
        }

        @Override
        protected void onPostExecute(final ArrayList<HashMap<String, String>> result) {
            super.onPostExecute(result);
            if (!result.isEmpty()) {
                EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));
                finish();
            } else {
                isRUN.set(false);
            }
        }
    }
ActivityUtil.finishExcept(MainActivity.class);

 3.4 Result:

Profiler analysis results show frequent GC recycling and severe memory jitter

5ace84e7195f4c8f852eb488c1398dd2.png

 The memory went from below 128 to over 200 within 5 minutes, nearly doubling

929712e3bbda4e29886d43ab334a1426.png

 Analyzing the results, you can see more than 300 memory leaks

7052131251b14d1ab6cfd6bd232a63fb.png

 Memory leaks caused by three placesd6ab7704ac174e49934b11c1e4125f42.png

leakcanary detection results leaks caused by ActivityUtils

76d9a344cb6a4f19959869bd1f5eb951.jpeg

0a6cdb3ea2d14d028dfccdf4db968d5e.jpeg

program crash

 3.5 Cause Analysis

The asynchronous task holds a reference to the activity. After the activity is closed but the task is not finished, the ActivityUtils tool class cannot finish the end page, resulting in the page not being able to be recycled, thus causing a memory leak.

3.6 Optimization solution

The first step is to optimize the camera scan frame preview, and use a buffered one instead

mCamera.addCallbackBuffer(new byte[parameters.getPreviewSize().width * parameters.getPreviewSize().height * 3 / 2]);
mCamera.setPreviewCallbackWithBuffer(previewCallback);

//Regardless of whether there is data or not, it is added to the buffer at the end of the preview
PreviewCallback previewCallback = new PreviewCallback() {
    public void onPreviewFrame(byte[] data, Camera camera) {
        camera.addCallbackBuffer(data);
    }
}
mCamera.setDisplayOrientation(270);//竖屏显示
mCamera.setPreviewDisplay(mHolder);
//mCamera.setPreviewCallback(previewCallback);
mCamera.addCallbackBuffer(new byte[parameters.getPreviewSize().width * parameters.getPreviewSize().height * 3 / 2]);
mCamera.setPreviewCallbackWithBuffer(previewCallback);
mCamera.startPreview();
mCamera.autoFocus(autoFocusCallback);
PreviewCallback previewCallback = new PreviewCallback() {
   public void onPreviewFrame(byte[] data, Camera camera) {    
	if (data != null) {
		if (isRUN.compareAndSet(false, true)) {
			Camera.Parameters parameters = camera.getParameters();
			Size size = parameters.getPreviewSize();//获取预览分辨率

			//创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的
			Image source = new Image(size.width, size.height, "Y800");
			//图片旋转了90度,将扫描框的TOP作为left裁剪
			source.setData(data);//填充数据

			ArrayList<HashMap<String, String>> result = new ArrayList<>();
			//解码,返回值为0代表失败,>0表示成功
			int dataResult = mImageScanner.scanImage(source);
			if (dataResult != 0) {
				playBeepSoundAndVibrate();//解码成功播放提示音
				SymbolSet syms = mImageScanner.getResults();//获取解码结果
				for (Symbol sym : syms) {
					HashMap<String, String> temp = new HashMap<>();
					temp.put(ScanConfig.TYPE, sym.getSymbolName());
					temp.put(ScanConfig.VALUE, sym.getResult());
					result.add(temp);
					if (!ScanConfig.IDENTIFY_MORE_CODE) {
						break;
					}
				}
				syms.destroy();
				syms = null;
				if (result.size() > 0) {
					isRUN.set(true);
					finish();
					EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));
				} else {
					isRUN.set(false);
				}
			} else {
				isRUN.set(false);
			}
		}
	}
    camera.addCallbackBuffer(data);
}

The second step is to remove the source code related to asynchronous tasks and ActivityUtils

Optimized result:

It can be seen that the memory has always been maintained below 128M in 30 minutes, and it is very stable

004ef0ccf9fa4da294d0ae619e4bcae9.png

 The final analysis result is also 0 leaks

448ef4d107ce46fb82c6d743d829bb20.png

 After 4 hours of continuous testing, the memory is still below 128M, which is a perfect solution

3.7 Summary:

  • When a memory leak occurs, first find the location of the leak. Leakcanary and Profiler analyze at the same time to quickly locate the location.
  • The main feature of leaks is that the memory keeps soaring, and it is easy to cause long-life cycle objects to hold short-life cycle objects, resulting in short-life cycle objects that cannot be released.
  • So pay attention to this point in development. When the short life cycle ends, you must end the long life cycle tasks first, and let the long life cycle objects release the short life cycle references, so that the short life cycle objects can be successfully terminated and recycled.
  • For a long life cycle, use the application context as much as possible to avoid objects with a short life cycle that cannot be released.

Guess you like

Origin blog.csdn.net/qq_29848853/article/details/129690899