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
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
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
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
The memory went from below 128 to over 200 within 5 minutes, nearly doubling
Analyzing the results, you can see more than 300 memory leaks
Memory leaks caused by three places
leakcanary detection results leaks caused by ActivityUtils
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
The final analysis result is also 0 leaks
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.