LMK;内存抖动、内存泄漏与内存溢出
优化的结果:使得app流畅不卡
内存抖动
短时间内有大量对象创建与销毁,它伴随着频繁的GC。
比较典型的就是字符串的拼接造成内存抖动。
比如:
String str = "";
for(int i=0; i< 10; i++) {
str += i;
}
+=操作会编译成StringBuilder对象,然后调用StringBuilder的append方法进行拼接。
所以上述代码会创建10个StringBuilder对象,每执行一次+操作都会新创建一个StringBuilder对象。
优化方法:避免使用+或者+=操作,使用StringBuilder来实现字符串的拼接
StringBuilder str = new StringBuilder();
for (int i = 0; i < 10; i++) {
str.append(i);
}
内存抖动实战
一个不断旋转的试图,优化前的代码:
public class IOSStyleLoadingView1 extends View {
private final Context context;
private double radius;
private double insideRadius;
private float northwestXStart;
private float northwestYStart;
private float northXStart;
private float northYStart;
private float notheastXStart;
private float notheastYStart;
private float eastXStart;
private float eastYStart;
private float southeastXStart;
private float southeastYStart;
private float southXStart;
private float southYStart;
private float southwestXStart;
private float southwestYStart;
private float westXStart;
private float westYStart;
private float northwestXEnd;
private float northwestYEnd;
private float northXEnd;
private float northYEnd;
private float notheastXEnd;
private float notheastYEnd;
private float eastXEnd;
private float eastYEnd;
private float southeastXEnd;
private float southeastYEnd;
private float southXEnd;
private float southYEnd;
private float southwestXEnd;
private float southwestYEnd;
private float westXEnd;
private float westYEnd;
private int currentColor = 7;
String color[] = new String[]{
"#a5a5a5",
"#b7b7b7",
"#c0c0c0",
"#c9c9c9",
"#d2d2d2",
"#dbdbdb",
"#e4e4e4",
"#e4e4e4"
};
public IOSStyleLoadingView1(Context context) {
this(context,null,0);
}
public IOSStyleLoadingView1(Context context, AttributeSet attrs) {
this(context,null,0);
}
public IOSStyleLoadingView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
radius = UIKits.dip2Px(context, 9);
insideRadius = UIKits.dip2Px(context, 5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(UIKits.dip2Px(context, 2));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
Path p0 = new Path();
paint.setColor(Color.parseColor(color[0]));//Color.parseColor方法会调用String的substring()方法,substring()方法会产生新的String对象
p0.moveTo(northwestXStart, northwestYStart);
p0.lineTo(northwestXEnd, northwestYEnd);
canvas.drawPath(p0, paint);
Path p1 = new Path();
paint.setColor(Color.parseColor(color[1]));
p1.moveTo(northXStart, northYStart);
p1.lineTo(northXEnd, northYEnd);
canvas.drawPath(p1, paint);
Path p2 = new Path();
paint.setColor(Color.parseColor(color[2]));
p2.moveTo(notheastXStart, notheastYStart);
p2.lineTo(notheastXEnd, notheastYEnd);
canvas.drawPath(p2, paint);
Path p3 = new Path();
paint.setColor(Color.parseColor(color[3]));
p3.moveTo(eastXStart, eastYStart);
p3.lineTo(eastXEnd, eastYEnd);
canvas.drawPath(p3, paint);
Path p4 = new Path();
paint.setColor(Color.parseColor(color[4]));
p4.moveTo(southeastXStart, southeastYStart);
p4.lineTo(southeastXEnd, southeastYEnd);
canvas.drawPath(p4, paint);
Path p5 = new Path();
paint.setColor(Color.parseColor(color[5]));
p5.moveTo(southXStart, southYStart);
p5.lineTo(southXEnd, southYEnd);
canvas.drawPath(p5, paint);
Path p6 = new Path();
paint.setColor(Color.parseColor(color[6]));
p6.moveTo(southwestXStart, southwestYStart);
p6.lineTo(southwestXEnd, southwestYEnd);
canvas.drawPath(p6, paint);
Path p7 = new Path();
paint.setColor(Color.parseColor(color[7]));
p7.moveTo(westXStart, westYStart);
p7.lineTo(westXEnd, westYEnd);
canvas.drawPath(p7, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
double centerX = getMeasuredWidth() / 2;
double centerY = getMeasuredHeight() / 2;
double leg = radius * Math.cos(Math.PI / 4);
double insideLeg = insideRadius * Math.cos(Math.PI / 4);
northwestXStart = (float) (centerX - leg);
northwestYStart = (float) (centerY - leg);
northXStart = (float) centerX;
northYStart = (float) (centerY - radius);
notheastXStart = (float) (centerX + leg);
notheastYStart = (float) (centerY - leg);
eastXStart = (float) (centerX + radius);
eastYStart = (float) centerY;
southeastXStart = (float) (centerX + leg);
southeastYStart = (float) (centerY + leg);
southXStart = (float) centerX;
southYStart = (float) (centerY + radius);
southwestXStart = (float) (centerX - leg);
southwestYStart = (float) (centerY + leg);
westXStart = (float) (centerX - radius);
westYStart = (float) centerY;
northwestXEnd = (float) (centerX - insideLeg);
northwestYEnd = (float) (centerY - insideLeg);
northXEnd = (float) centerX;
northYEnd = (float) (centerY - insideRadius);
notheastXEnd = (float) (centerX + insideLeg);
notheastYEnd = (float) (centerY - insideLeg);
eastXEnd = (float) (centerX + insideRadius);
eastYEnd = (float) centerY;
southeastXEnd = (float) (centerX + insideLeg);
southeastYEnd = (float) (centerY + insideLeg);
southXEnd = (float) centerX;
southYEnd = (float) (centerY + insideRadius);
southwestXEnd = (float) (centerX - insideLeg);
southwestYEnd = (float) (centerY + insideLeg);
westXEnd = (float) (centerX - insideRadius);
westYEnd = (float) centerY;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimation();
}
private ValueAnimator valueAnimator;
public void startAnimation() {
valueAnimator = ValueAnimator.ofInt(7, 0);
valueAnimator.setDuration(400);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if ((int) animation.getAnimatedValue() != currentColor) {
String b[] = new String[color.length];//移动后的数组
for (int c = 0, size = color.length - 1; c < size; c++) {
b[c + 1] = color[c];
}
b[0] = color[color.length - 1];
color = b;
invalidate();
currentColor = (int) animation.getAnimatedValue();
}
}
});
valueAnimator.start();
}
}
利用Android Studio内置的Profiler工具查看内存使用情况:
可以看到内存产生抖动,并且随着程序的运行,内存一直在增加,选择一段时间分析内存中的对象:
可以看到内存中的有大量的Path对象和String对象,一般来说是有异常的,分析代码看看是否能避免这种情况。
可以在onDraw方法里进行优化,优化后的代码:
/**
* 优化后的IOSStyleLoadingView
* onDraw方法里不创建Path和Paint对象,不调用Color.parseColor创建String对象
*
*/
public class IOSStyleLoadingView extends View {
private final Context context;
private double radius;
private double insideRadius;
private float northwestXStart;
private float northwestYStart;
private float northXStart;
private float northYStart;
private float notheastXStart;
private float notheastYStart;
private float eastXStart;
private float eastYStart;
private float southeastXStart;
private float southeastYStart;
private float southXStart;
private float southYStart;
private float southwestXStart;
private float southwestYStart;
private float westXStart;
private float westYStart;
private float northwestXEnd;
private float northwestYEnd;
private float northXEnd;
private float northYEnd;
private float notheastXEnd;
private float notheastYEnd;
private float eastXEnd;
private float eastYEnd;
private float southeastXEnd;
private float southeastYEnd;
private float southXEnd;
private float southYEnd;
private float southwestXEnd;
private float southwestYEnd;
private float westXEnd;
private float westYEnd;
private int currentColor = 7;
String color[] = new String[]{
"#a5a5a5",
"#b7b7b7",
"#c0c0c0",
"#c9c9c9",
"#d2d2d2",
"#dbdbdb",
"#e4e4e4",
"#e4e4e4"
};
int[] colors = new int[8];
public IOSStyleLoadingView(Context context) {
this(context, null, 0);
}
public IOSStyleLoadingView(Context context, AttributeSet attrs) {
this(context, null, 0);
}
/**
* 提前在构造方法里调用Color.parseColor解析好数据,而不是在onDraw方法里频繁调用
*
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public IOSStyleLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
radius = UIKits.dip2Px(context, 9);
insideRadius = UIKits.dip2Px(context, 5);
for (int i = 0; i < color.length; i++) {
colors[i] = Color.parseColor(color[i]);
}
paint.setAntiAlias(true);
paint.setStrokeWidth(UIKits.dip2Px(context, 2));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
}
Path path = new Path();
Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(northwestXStart, northwestYStart);
path.lineTo(northwestXEnd, northwestYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(northXStart, northYStart);
path.lineTo(northXEnd, northYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(notheastXStart, notheastYStart);
path.lineTo(notheastXEnd, notheastYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(eastXStart, eastYStart);
path.lineTo(eastXEnd, eastYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(southeastXStart, southeastYStart);
path.lineTo(southeastXEnd, southeastYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(southXStart, southYStart);
path.lineTo(southXEnd, southYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(southwestXStart, southwestYStart);
path.lineTo(southwestXEnd, southwestYEnd);
canvas.drawPath(path, paint);
path.reset();
paint.setColor(colors[(currentColor++) % 8]);
path.moveTo(westXStart, westYStart);
path.lineTo(westXEnd, westYEnd);
canvas.drawPath(path, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
double centerX = getMeasuredWidth() / 2;
double centerY = getMeasuredHeight() / 2;
double leg = radius * Math.cos(Math.PI / 4);
double insideLeg = insideRadius * Math.cos(Math.PI / 4);
northwestXStart = (float) (centerX - leg);
northwestYStart = (float) (centerY - leg);
northXStart = (float) centerX;
northYStart = (float) (centerY - radius);
notheastXStart = (float) (centerX + leg);
notheastYStart = (float) (centerY - leg);
eastXStart = (float) (centerX + radius);
eastYStart = (float) centerY;
southeastXStart = (float) (centerX + leg);
southeastYStart = (float) (centerY + leg);
southXStart = (float) centerX;
southYStart = (float) (centerY + radius);
southwestXStart = (float) (centerX - leg);
southwestYStart = (float) (centerY + leg);
westXStart = (float) (centerX - radius);
westYStart = (float) centerY;
northwestXEnd = (float) (centerX - insideLeg);
northwestYEnd = (float) (centerY - insideLeg);
northXEnd = (float) centerX;
northYEnd = (float) (centerY - insideRadius);
notheastXEnd = (float) (centerX + insideLeg);
notheastYEnd = (float) (centerY - insideLeg);
eastXEnd = (float) (centerX + insideRadius);
eastYEnd = (float) centerY;
southeastXEnd = (float) (centerX + insideLeg);
southeastYEnd = (float) (centerY + insideLeg);
southXEnd = (float) centerX;
southYEnd = (float) (centerY + insideRadius);
southwestXEnd = (float) (centerX - insideLeg);
southwestYEnd = (float) (centerY + insideLeg);
westXEnd = (float) (centerX - insideRadius);
westYEnd = (float) centerY;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimation();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (valueAnimator != null) {
valueAnimator.removeAllUpdateListeners();//移除监听器,否则会造成内存泄漏
valueAnimator.cancel();//取消动画
}
}
private ValueAnimator valueAnimator;
public void startAnimation() {
valueAnimator = ValueAnimator.ofInt(7, 0);
valueAnimator.setDuration(400);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if ((int) animation.getAnimatedValue() != currentColor) {
// String b[] = new String[color.length];//移动后的数组
// for (int c = 0, size = color.length - 1; c < size; c++) {
// b[c + 1] = color[c];
// }
// b[0] = color[color.length - 1];
// color = b;
invalidate();
currentColor = (int) animation.getAnimatedValue();
}
}
});
valueAnimator.start();
}
}
再次查看内存使用情况:
可以看到内存平稳,选择一段时间分析内存中的对象:
可以看到内存中不会再有大量的对象。
预防内存抖动
1.避免在循环中创建对象;
2.避免在频繁调用的方法中创建对象,如View的onDraw方法;
3.允许复用的情况下,使用对象池进行缓存,如:Handler的Message单链表(obtain);
内存抖动会造成的问题:卡顿和OOM
卡顿
内存抖动会引起频繁的gc,gc是会STW的,会暂停用户线程。
OOM
CMS垃圾回收器老年代是标记-清除算法:不会移动存活的对象,会产生内存碎片。
像上述图中的内存,虽然有很多内存可用,但却是不连续的,如果申请连续的10个字节(假设图中一个空格代表一个字节)的内存就会产生OOM,因为没有连续的10个字节的可用内存。
比如申请bitmap时就很可能产生OOM。
内存泄漏
程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。
长生命周期对象持有短生命周期对象强引用,从而导致短生命周期对象无法被回收!
注意这里是持有对象的强引用,如果是持有对象的软引用或者是弱引用或者是虚引用则不会造成内存泄漏。
可达性分析法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
GC Roots
在Java语言中可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
- JVM的内部引用(class对象、异常对象NullPointException、OutofMemoryError,系统类加载器)。
- 所有被同步锁(synchronized关键)持有的对象。
- JVM内部的JMXBean、JVMTI中注册的回调、本地代码缓存等
- JVM实现中的“临时性”对象,跨代引用的对象(在使用分代模型回收只回收部分代时)
也可以参考:
Garbage Collection Roots
Garbage Collection Roots
A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root:
System Class
Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .
JNI Local
Local variable in native code, such as user defined JNI code or JVM internal code.
JNI Global
Global variable in native code, such as user defined JNI code or JVM internal code.
Thread Block
Object referred to from a currently active thread block.
Thread
A started, but not stopped, thread.
Busy Monitor
Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.
Java Local
Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
Native Stack
In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection.
Finalizable
An object which is in a queue awaiting its finalizer to be run.
Unfinalized
An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.
Unreachable
An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.
Java Stack Frame
A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.
Unknown
An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.
软引用,弱引用
软引用:定义一些还有用但并非必须的对象。对于软引用关联的对象,GC不会直接回收,而是在系统将要内存溢出之前才会触发GC将这些对象进行回收。
弱引用:同样定义非必须对象。被弱引用关联的对象在GC执行时会被直接回收。
Handler的内存泄露场景
public class MemoryLeakActivity extends Activity {
private int i = 10;//成员属性i
private Handler mHandler = new Handler();
/**
* 如果不传外部类引用则不能访问外部类的成员属性i
*/
static class Runnable1 implements Runnable {
@Override
public void run() {
//但是静态内部类不能访问外部类的成员属性i,怎么办?将外部类引用传递给静态内部类
//System.out.println("i=" + i);
}
}
/**
* 如果直接传递外部类引用则依然会造成内存泄露
*/
static class Runnable2 implements Runnable {
//但是不能直接将外部类引用传递进来,因为虽然可以访问外部类的成员属性,但是还是会造成内存泄露
MemoryLeakActivity activity;
public Runnable2(MemoryLeakActivity activity) {
this.activity = activity;
}
@Override
public void run() {
System.out.println(activity.i);
}
}
/**
* 正确解决方案:使用弱引用,即静态内部类持有外部类的弱引用
*/
static class Runnable3 implements Runnable {
/**
* 注意这里应该使用WeakReference,而不是SoftReference,
* 虽然使用SoftReference也不会造成OOM,但是当我们退出Activity时是希望Activity尽快被回收的,
* 所以使用WeakReference更合适,因为被WeakReference关联的对象在GC执行时会被直接回收,
* 而对于SoftReference关联的对象,GC不会直接回收,而是在系统将要内存溢出之前才会触发GC将这些对象进行回收。
*/
WeakReference activityWeakReference;
public Runnable3(MemoryLeakActivity activity) {
this.activityWeakReference = new WeakReference(activity);
}
@Override
public void run() {
if (activityWeakReference.get() != null) {
int i = activityWeakReference.get().i;
System.out.println("i=" + i);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memoryleak);
/**
* Handler的内存泄露场景
*/
mHandler.postDelayed(new Runnable() {//这里创建了一个匿名内部类对象
@Override
public void run() {
//匿名内部类对象持有外部类的引用(即持有Activity的引用),所以可以直接访问外部类的的成员属性i
//所以会造成内存泄露
System.out.println("i=" + i);
}
}, 1_000);
//解决方案1:在onDestroy()方法里调用 mHandler.removeCallbacksAndMessages(null);将消息队列里的Message全部移除
//解决方案2:使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用
mHandler.postDelayed(new Runnable3(this), 10_000);
}
@Override
protected void onDestroy() {
super.onDestroy();
//解决方案1:在onDestroy()方法里调用 mHandler.removeCallbacksAndMessages(null);将消息队列里的Message全部移除
mHandler.removeCallbacksAndMessages(null);
}
MAT检测内存泄漏实战
1.打开Android studio的profiler的Memory视图
2.点击按钮进入SecondActivity,退出,再次点击按钮进入SecondActivity,退出。
3.点击dump按钮生成内存快照
dump完成后Android studio会开启一个新的视图显示当前内存中的所有对象
点击Arrange by package选项让所有类按照包进行组织,方便找到自己写的类,找到SecondActivity,可以看到内存中有2个SecondActivity对象仍然存活,但这能代表SecondActivity就发生内存泄漏吗?是不能的,因为只有强引用指向SecondActivity对象时才代表发生内存泄露,如果是软引用、弱引用、虚引用指向SecondActivity对象是不会发生内存泄漏的,但是Android studio的profiler无法判断是强引用、软引用、弱引用、虚引用的,需要借助MAT工具。
\4. 导出hprof文件,点击导出按钮将Android studio中的hprof文件导出到自己的文件夹,用于后续在MAT工具中分析。
5.导出的hprof文件是无法直接在MAT工具中分析的,如果直接在MAT中打开这个文件会报错,需要进行转换,转换工具是sdk/platform-tools里面的hprof-conv.exe工具,执行命令进行转换:
6.在MAT中打开转换后的文件,打开后选择Histogram视图,输入SecondActivity进行过滤,可以看到SecondActivity对象有2个,右键选择SecondActivity对象,选择merge shortest paths to gc roots -> exclude all phantom/weak/soft etc. references,排除掉虚引用/弱引用/软引用,只保留强引用,因为只有强引用才会导致内存泄露。
7.分析强引用的引用链:
ValueAnimator的mUpdateListeners成员属性持有IOSStyleLoadingView的内部类对象,该内部类对象持有外部类的引用this$0(即持有IOSStyleLoadingView的引用),而IOSStyleLoadingView是一个View,它的context属性(IOSStyleLoadingView中定义的)和mContext属性(View中定义的,View的mContext属性默认持有View所在Activity的引用)持有SecondActivity的引用。
分析下来原因就是valueAnimator添加的监听器没有移除:
public void startAnimation() {
valueAnimator = ValueAnimator.ofInt(7, 0);
valueAnimator.setDuration(400);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if ((int) animation.getAnimatedValue() != currentColor) {
// String b[] = new String[color.length];//移动后的数组
// for (int c = 0, size = color.length - 1; c < size; c++) {
// b[c + 1] = color[c];
// }
// b[0] = color[color.length - 1];
// color = b;
invalidate();
currentColor = (int) animation.getAnimatedValue();
}
}
});
valueAnimator.start();
}
解决方法:在onDetachedFromWindow中移除监听器,并取消动画
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (valueAnimator != null) {
valueAnimator.removeAllUpdateListeners();//移除监听器,否则会造成内存泄漏
valueAnimator.cancel();//取消动画
}
}
8.重新执行1-7步,可以看到内存中SecondActivity还是有2个
在这里插入图片描述
这是否意味着仍然存在内存泄露?右键选择SecondActivity对象,选择merge shortest paths to gc roots -> exclude all phantom/weak/soft etc. references
可以看到已经没有强引用的引用链了,说明不会发生内存泄露。
那么到底是谁引用着SecondActivity?接下来选择merge shortest paths to gc roots -> exclude weak references:
说明只有FinalizerReference引用着SecondActivity,即SecondActivity已经即将被回收了。
9.如果有很多页面,那是否要每个页面都执行上述操作?这需要大量的操作,有没有更好的办法进行一次性检测?有,可以进行内存快照的比对,如何操作?
首次进入主页面时dump一次内存快照,然后不断进出各个页面,再次返回到主页面时又dump一次内存快照,然后比对这两次的hprof文件。
图中显示3.hprof比1.hprof的相应对象多了多少个。
内存泄漏问题常见场景
1. 集合类
当使用集合时,只有添加元素,没有对应的删除元素。如EventBus只有注册没有注销!
2. 静态成员/单例
作为GC ROOT,持有短生命周期对象的引用(如持有Activity对象的引用)导致短生命周期对象无法释放。
/**
* 内存泄漏问题常见场景
* 静态成员/单例:作为GC ROOT,持有短生命周期对象的引用(如持有Activity对象的引用)导致短生命周期对象无法释放。
*/
public class SingletonManager {
//GC ROOT:静态属性所引用的对象,这里new SingletonManager()创建的SingletonManager对象就是属于GC ROOT
private static final SingletonManager ourInstance = new SingletonManager();
private Context mContext;
public static SingletonManager getInstance() {
return ourInstance;
}
private SingletonManager() {
}
/**
* 解决办法:mContext传递ApplicationContext,而不是传递Activity
* 否则静态对象SingletonManager的成员属性mContext会一直持有Activity对象,导致Activity内存泄露
*
* @param context
*/
public void init(Context context) {
mContext = context;
}
public class MemoryMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_main);
SingletonManager.getInstance().init(getApplicationContext());
}
}
3. 未关闭/释放资源
如FileOutputStream未close。
/**
* 未关闭/释放资源导致的内存泄露
*/
private void write() {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File(""));
fos.write(1);//fos.write(1);可能会出现异常,导致fos.close()不能执行,从而资源未关闭
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 正确写法
*/
private void write1() {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File(""));
fos.write(1);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4. 非静态内部类
如Handler postDelayed一个匿名Runnable,退出Activity时消息没处理完
5. 系统Bug
WebView、InputMethodManager等
/**
* 系统BUG:InputMethodManager导致的内存泄露。
*/
private void resolveInputMethodManagerMemoryLeak() {
// 系统BUG:InputMethodManager导致的内存泄露。
// InputMethodManager是一个static单例对象(所以InputMethodManager是GC root),InputMethodManager的mCurRootView、mNextServedView、mServedView持有DecorView的引用,
// DecorView持有Activity的引用,导致Activity不能被回收。
//解决办法:将InputMethodManager的mCurRootView、mNextServedView、mServedView置为null
InputMethodManager im = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
try {
Field mCurRootView = InputMethodManager.class.getDeclaredField("mCurRootView");
mCurRootView.setAccessible(true);
mCurRootView.set(im, null);
Field mNextServedView = InputMethodManager.class.getDeclaredField("mNextServedView");
mNextServedView.setAccessible(true);
mNextServedView.set(im, null);
Field mServedView = InputMethodManager.class.getDeclaredField("mServedView");
mServedView.setAccessible(true);
mServedView.set(im, null);
} catch (Exception e) {
e.printStackTrace();
}
}