在我们Android开发中随着功能的不断增加,页面的不断增多,我们就会面临一个难题,那就是内存泄漏那么好,那如何解决内存泄漏,我下面给出一篇,完全解析内存泄漏这一难题的文章,供大家开发者参考.
每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享。Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量。虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被创建的对象的本身,而在栈中分配的内存只是存放指向这个堆对象的引用而已。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。
具体的概念:JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method,也叫静态区):
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息(class的目的是得到操作指令) ;
2.jvm只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身和数组本身;
1.每个线程包含一个栈区,栈中只保存基础数据类型本身和自定义对象的引用;
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问;
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令);
1.被所有的线程共享,方法区包含所有的class(class是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
handler耗时引发的内存泄漏:
当activity当中存在handler接收耗时的消息时,比如我们一般在网络请求切换线程时,经常使用到handler,假设消息还没有发送完成,但是页面已经被关闭,也就说activity已经执行了ondestroy方法。当gc回收时,会出现改activity不能被回收的情况,到时内存泄漏。
解决办法:当activity销毁的时候,调用handler的removeCallbacksAndMessages方法,移除消息任务,然后将handler对象及线程置空。
内部类引发的内存泄漏(当然handler或子线程一般也作为内部类使用)
因为java当中,内部类默认持有外部类的引用,当外部类销毁后,一旦gc回收该实例,发现内部类持有他的引用而导致不能回收该实例,出现内存泄漏的情况。
解决方法:将内部类改为静态内部类,因为静态内部类生命周期和应用一样长,所以当退出程序的时候会一同回收该实例,并不会影响外部类的回收。
单例导致的内存泄漏
因为在使用单例的时候,经常会传入一个本类的上下文对象,而单例是静态的,生命周期和application一样长,当activity销毁的时候,该单例持有activity的引用导致其不能被回收,出现内存泄漏。
解决方法:在使用上下文的时候,传全局上下文。
.资源未关闭
Cursor,stream,database,Butterknife,broadcastreciver,bindservice,eventBus
比如这些东西在使用完成后,需要进行close或者Unbind处理,以节省内存
Timer计时器、动画
因为这些涉及耗时问题,如果activity销毁,而该任务并未执行完成,会导致内存泄漏,所以一般在activity中如果使用到这些耗时任务,需要在activity销毁时,做对应处理,比如调用timer的cancel方法,或者动画的cancel方法并将对象置空
一些监听器的内存泄漏
比如说我们给edittext设置输入文字监听时,当监听到文字发生变化,我们通过获取变化后的文字执行了耗时任务(比如获取到edittext里的内容上传服务器),当耗时任务未执行完成activity销毁了,会引发内存泄漏,所以在onDestory时,取消注册,比如说editText调用removeTextChangedListener方法
Rxjava的内存泄漏:
因为rxjava采用的是观察者模式,当请求到数据后会根据订阅关系将数据发送个订阅者,而如果这时订阅者已经销毁,就会出现引用该对象导致其不能被回收的情况,出现内存泄漏,rxjava2发布的时候也发现了这个问题,所以在回调当中,新增加了onSubcribe回调,同时返回了一个disposable对象,可以通过判断disposable里的isDisposed来确定当前的订阅关系,如果订阅关系中的订阅者已经不存在且当前订阅关系存在,解除订阅关系,并终止数据的发送。
webView引发的内存泄漏:
因为webview在使用的时候一般持有activity的引用,我们一般在activity的onDestroy方法中调用mWebView.destroy();来释放webview。如果在onDetachedFromWindow之前调用了destroy那就肯定会无法正常反注册了,也就会导致内存泄漏。所以在销毁webview前一定要先在onDetachedFromWindow中将webview从它的父view中移除,再调用destroy方法中调用webview的destroy,我开发的时候在5.1以上的手机上发现这种问题比较多,因为现在5.1以下适配的比较少了,基本没咋注意。
线程导致的内存泄漏:
一般使用子线程都会创建一个内部类对象,而创建线程一般执行耗时任务,所以这个内部类默认持有外部类的引用,如果耗时任务在activity销毁的时候未执行完成,会因为持有外部类引用导致外部类不能被回收
10.MVP内存泄漏:
当我们使用MVP的模式进行写代码时,由于页面已经销毁但网络请求仍然在,会导致Presenter一直持有Activity的对象,造成内存泄漏。
解决方法:在关闭页面时把网络请求给切断,把Prseenter引用置空,这样可以做到完全解耦而又不会导致内存泄漏.
Bitmap:
如果图片像素过大,使用BitmapFactory类的方法实例化Bitmap的过程中,需要大于8M的内存空间,就必定会发生OutOfMemory异常。这个时候该如何处理呢?如果有这种情况,则可以将图片缩小,以减少载入图片过程中的内存的使用,避免异常发生。
BitmapFactory提供了几种解码方式(decodeByteArray(),decodeFile(), decodeResource()等
等), 以便从多种资源中创建一个Bitmap(位图)对象。可以根据你的图片数据来源选择最合适的解码方 式.
每一种解码方式都有额外的特征,你可以通过BitmapFactory.Options类指定解码方法。
1 | BitmapFactory.Options options = new BitmapFactory.Options(); |
---|---|
2 | options.inJustDecodeBounds = true; |
3 | BitmapFactory.decodeResource(getResources(), R.id.myimage, options); |
---|---|
4 | int imageHeight = options.outHeight; |
5 | int imageHeight = options.outHeight; |
---|---|
6 | String imageType = options.outMimeType; |
1.1 LeakCanart简单介绍
1.2 原理:
1.3 4种引用
3.github官网:https://github.com/square/leakcanary
4.gradle依赖:
implementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
5.使用很简单: 在Application的onCreate 写入LeakCanary.install(this)
1.install源码分析
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
//分析buildAndInstall
.buildAndInstall();
}
buildAndInstall源码分析
public RefWatcher buildAndInstall() {
//返回refWatcher
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
//弹出告诉你那里泄漏,表示开启activity
LeakCanary.enableDisplayLeakActivity(context);
//继续看这个
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
ActivityRefWatcher.install源码分析
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
// 反注册我们以前的activitycallback
stopWatchingActivities();
//重新注册 看lifecycleCallbacks源码
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
lifecycleCallbacks源码很长只要关注一行就可以了
@Override public void onActivityDestroyed(Activity activity) {
//和Application的onDestory的生命周期关联
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
void onActivityDestroyed(Activity activity) {
// 首先看下refWatcher有哪些成员变量,然后看watch的源码
refWatcher.watch(activity);
}
//refWatcher成员变量
//用于内存检测
private final WatchExecutor watchExecutor;
//查询是否正在调试中
private final DebuggerControl debuggerControl;
//用于判断泄漏之前,给gc最后一次机会
private final GcTrigger gcTrigger;
//内存泄漏堆文件
private final HeapDumper heapDumper;
//key
private final Set<String> retainedKeys;
//判断弱应用所持有的对象是否被已经被gc垃圾回收
private final ReferenceQueue<Object> queue;
private final HeapDump.Listener heapdumpListener;
//排除一些系统bug
private final ExcludedRefs excludedRefs;
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
//添加的唯一的key
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//看这个源码
ensureGoneAsync(watchStartNanoTime, reference);
}
ensureGoneAsync源码分析
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
//线程
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
//持续时间
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//移除已达到队列中的弱应用
removeWeaklyReachableReferences();
//如果是处于调试中则不进行分析
if (debuggerControl.isDebuggerAttached()) {
return RETRY;
}
if (gone(reference)) {
return DONE;
}
//手动执行gc
gcTrigger.runGc();
//移除已达到队列中的弱应用
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
```
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//最后在线程中取开始分析我们的泄漏
heapdumpListener.analyze(
```
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
analyze分析我们的泄漏
void analyze(HeapDump heapDump);
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
HeapAnalyzerService.runAnalysis源码分析,因为这个方法是继承于IntentService方法,所以会回调onHandlerIntent方法
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
@Override protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
//获得传来的值
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
//排除一些系统bug
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
//这个很重要
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
//回调
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
checkForLeak源码分析
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
//heapDumpFile封装成对象
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//解释器解析上面的buffer
HprofParser parser = new HprofParser(buffer);
//具体的解析工作
Snapshot snapshot = parser.parse();
//对我们所检查的结果去重
deduplicateGcRoots(snapshot);
//获得解析的结果
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
if (leakingRef == null) {//表示对象不存在,表示已经清楚
return noLeak(since(analysisStartNanoTime));
}
//找出泄漏的对象/找出泄漏对象的最短路径
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
1.把.hprof转化为Snapshot 2.优化gcroots 3.找出泄漏的对象/找出泄漏对象的最短路径
findLeakingReference源码分析
private Instance findLeakingReference(String key, Snapshot snapshot) {
//根据弱应用查找类的对象
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
List<String> keysFound = new ArrayList<>();
//遍历这个对象的所有实例
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
String keyCandidate = asString(fieldValue(values, "key"));
//如果key的值和最开始定义封装的key值相同,那么返回这个泄漏对象
if (keyCandidate.equals(key)) {
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
findLeakTrace源码分析:
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) {
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
//屏幕显示的就是这个
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
//计算内存泄漏的空间大小的
long retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime));
}
本作品采用知识共享署名 4.0 国际许可协议进行许可。