组装托马斯小火车2,Android 轻松处理内存走漏,太平洋汽车

微博热点 · 2019-04-10

基础知识

Java 的内存分配简述


  • 办法区(non-heap):编译时就分配好,在程序整个运转期间都存在。它首要寄存静态数据和常量;
  • 栈区:当办法履行时,会在栈区内存中创立办法体内部的局部变量,办法完毕后自动开释内存;
  • 堆区(heap):一般用来寄存 new 出来的目标。由 GC 担任收回。

Java四种不同的引证类型

  • 强引证(Strong Reference):JVM 甘愿抛出 OOM,也不会让 GC 收回存在强引证的目标。
  • 软引证(Soft Reference) :一普寿寺落发女孩的感触个目标只具有软引证,在内存不足时,这个目标才会被 GC 收回。
  • 弱引证(weak Reference):在 GC 时,如姑姑的英文果一个目标只存在弱引证,那么它将会被收回。
  • 虚引证(Phantom Reference):任何时分都能够被 GC 岳瑞霞收回,当废物收回器预备收回一个目标时,假如发现它还有虚引证,就会在收回目标的内存之前,把这个虚引证加入到与之相关的引证行列中。程序能够经过判别引证行列中是否存在该目标的虚引证,来了解这个目标是否将要被收回。能够用来作为 GC 收回 Object 的标志。

与 Android 中的差异:在 2.3 今后版别中,即便内存够用,Android 体系会优先将 SoftReference 的目标提早收回掉, 其他和 Java 中是相同的。

因而谷歌小洋葱说明官方主张用LruCache(least recentlly use 最少最近运用算法)。会将内存操控在必定的巨细内, 超出最大值时会自动收回, 这个最大值开发者自己定。

什么是内存走漏?

  • 关于 C++ 来说,内存走漏便是 new 出来的目标没有 delete,俗称野指针;
  • 而关于 java 而言,便是寄存在堆上的 Object 无法被 GC 正常收回。

内存走漏根本原因

长生命周期的目标持有短生命周期目标**强/软引证**,导致本应该被收回的短生命周期的目标却无法被正常收回。

例如在单例形式中,咱们常常在获取单例目标时需求传一个 Context 。单例目标是一个长生命周期的目标(运用程序完毕时才完结),而假如咱们传递的是某一个 Activity 作为 context,那么这个 Activity 就会因为引证被持有而无法毁掉,然后导致内存走漏。

内存走漏的损害

  • 运转功用的问题: Android在运转的时分,假如内存走漏将导致其他组件可用的内存变少,一方面会使得GC的频率加重,在发作GC的时分,一切进程都有必要进行等候,GC的频率越多,然后用户越简单感知到卡顿。另一方面,内存变少,将或许使得体系会额定分配给你一些内存性感蕾丝,而影响整个体系的运转状况。
  • 运转崩吕成功简历溃问题: 内存走漏是内存溢出(沈途祝浅绿OOM)的重要原因之一,会导致 Crash。假如运用程序在耗费光了一切的可用堆空间,那么再企图在堆上分配新目标时就会引起 OOM(Out O林芷嘉f Memory Error) 反常,此刻运用程序就会溃散退出。

内存走漏的典型事例

永久的单例(Singleton)

因为单例形式的静态特性,使得它的生命周期和咱们的运用相同长,一不小心让单例无限制的持有 Activity 的强引证就会导致内存走漏。

处理方案

  • 把传入的 Context 改为同运用生命周期相同长的 Application 中的 Context。
  • 经过重写 Application,供给 getContext 办法,那样就不需求在获取单例时传入 037112340context。
public class BaseApplication extends Application{
private static ApplicationContext sContext;
@Override
public void onCreate(){
super.onCreate();
sContext = getApplicationContext();
}
public static Context getApplicationContext(){
return sContext;
}
}

Handler引发的内存走漏

因为 Handler 归于 TLS(Thread Local Storage)变量,导致它的生命周期和 Activity 不共同。因而经过 Handler 来更新 UI 一般很难确保跟 View 或许 Activity 的生命周期共同,故很简单导致无法正确开释。

例如:

public class HandlerBadActivity extends AppCompatActivity {
private final Handler handler = new Handler(){//非静态内部类,持有外部类的强引证
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protect钳花小包ed void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_bad);
// 推迟 5min 发送一个音讯
handler.postDelayed(new Runnable() {
//内部会将该 Runable 封装为一个 Message 目标,一起将 Message.target 赋值为 handler
@Override
public void run() {
//do something
}
}, 1000 * 60 * 5);
this.finish();
}
}

上面的代码中发送了了一个延时 5 分钟履行的 Message,当该 Activity 退出的时分,延时使命(Message)还在主线程的 MessageQueue 中等候,此刻的 Message 持有 Handler 的强引证(创立时经过 Message.target 进行指定),而且因为 Handler 是 HandlerBadActivity 的非静态内部类,所以 Handler 会持有一个指向 HandlerBadAc林佑威老婆tivity 的强引证,所以尽管此刻 HandlerBadActivity 调用了 finish 也无法进行内存收回,形成内存走漏。

处理办法

将 Handler 声明为静态内部类,可是要注意**假如用到 Context 等外部类的 非static 目标,仍是应该运用 ApplicationContext 或许经过弱引证来持有这些外部目标**。

public class HandlerGoodActivity extends AppCompatActivity {
private static final class MyHandler extends Handler{//声明为静态内部类(防止持有外部类的强引证)
private final WeakReference mActivity;
public MyHandler(HandlerGoodActivity activity){
this.mActivity = new WeakReference(activity);//运用弱引证
}
@Override
public void handleMessage(Message msg) {
HandlerGoodActivity activity = mActivity.get();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {//判别 activity 是否为空,以及是否正在被毁掉、或许现已被毁掉
removeCallbacksAndMessages(null);
return;
}
// do something
}
}
private final MyHandler myHandler = new MyHandler(this);
}

慎用 static 成员变量

static 润饰的变量坐落内存的办法区,其生命周期与 App 的生命周期共同。 这必然会导致一系列问题,假如你的 app 进程规划上是长驻内存的,那即便 app 切到后台,这部分内存也不会被开释。

处理办法

不要在类初始化时初始化静态成员,也便是能够考虑懒加载。架构规划上要考虑是否真的有必要这样做,尽量防止。假如架构需求这么规划,那么此目标的生命周期你有职责办理起来。

当然,Application 的 context 不是全能的,所以也不能随意乱用,关于有些当地则有必要运用 Activity 的 Context,关于Application,Service,Activity三者的Context的运用场景如下:

功用App拼装托马斯小火车2,Android 轻松处理内存走漏,太平洋轿车licationServiceActivityStart an ActivityNO1NO1YESShow a DialogNONOYESLayout InflationYESYESYESStart an ServiceYESYESYESBind an ServiceYESYESYESSend a BroadcastYESYESYESRegister BroadcastReceiverYESYESYESLoad Resource ValuesYESYESYES

  • NO1 表明 Application 和 Service 可惠水县百鸟河风景区以发动一个 Activity,不过需求创立一个新的 task 使命行列。
  • 关于 Dialog 而言,只需在 Activity 中才干创立。

运用体系效劳引发的内存走漏

为了便利咱们运用一些常见的体系效劳,Activity 做了一些封装。比方说,能够经过 getPackageManager在张牧阅 Activtiy 中获取 PackageManagerService,可是,里边实践上调用了 Activity 对应的 ContextImpl 中的 getPackageManager 办法

ContextWrapper#getPackageMan金焰和秦文ager

@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}

ContextImpl#getPackageMan拼装托马斯小火车2,Android 轻松处理内存走漏,太平洋轿车ager

@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(th拼装托马斯小火车2,Android 轻松处理内存走漏,太平洋轿车is, pm));//创立 ApplicationPackageManager
}
return null;
}

ApplicationPackageManager#ApplicationPackageManager

ApplicationPackageManager(ContextImpl context,
IPackageManage起舞捣蒜r pm) {
mContext = context;//保存 ContextImpl 的强引证
mPM = pm;
}
private UserManagerService(Con胸猛text context, PackageManagerService pm,
Object packagesLock, File dataDir) {
mContext = context;//持有外部 Context 引证
mPm = pm;
//代码省掉
}

PackageManagerService#PackageManagerService

public class PackageManagerService extends IPackageManager.Stub {
static UserManagerService sUserManager;//持有 UMS 静态引证
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
}
}

遇到的内存走漏问题是因为在 Activity 中调用了 getPackageManger 办法获取 PMS ,该办法调用的是 ContextImpl,此刻假如ContextImpl 中 PackageManager 为 null,就会创立一个 PackageManger(ContextImpl 会将自己传递进去,而 ContextImpl 的 mOuterContext 为 Activity),创立 PackageManager 实践上会创立 PackageManagerService(简称 PMS),而 PMS 的结构办法中会创立一个 U詹芳珍serManger(UserManger 初始化之后会持有 ContextImpl 的强引证)。

只需 PMS 的 class 未被毁掉,那么就会一向引证着 UserManger ,从而导致其相关到的资源无法正常开释。

处理办法

将getPackageManager()改为 getApplication()#getPackageManager() 。这样引证的便是 Application Context,而非 Activity 了。

远离非静态内部类和匿名类

因为运用非静态内部类和匿名类都会默许持有外部类的引证,假如生命周期不共同,就会导致内存走漏。

public class NestedClassLeakActivity extends AppCompatActivity {
class InnerClass {//非静态内部类
}
private static InnerClass sInner;//指向深圳市深迈医疗设备有限公司非静态内部类的静态引证
@Override
protected void onCreate(Bundle savedInstanceState拼装托马斯小火车2,Android 轻松处理内存走漏,太平洋轿车) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nested_class);
if (sInner == null) {
sInner = new InnerClass();//创立非静态内部类的实例
}
}
}

非静态内部类默许会持有外部类的引证,而外部类中又有一个该非静态内部类的静态实例,该静态实例的生命周期和运用的相同长,而静态实例又持有 Activity 的引证,因而导致 Activity 的内存资源不能正常收回。

处理办法

将该内部类设为静态内部类 也能够将该内部类抽取出来封装成一个单例

调集引发的内存走漏

咱们一般会把一些目标的引证加入到调集容器(比方ArrayList)中,当咱们不再需求该目标时(一般会调用 remove 办法),并没有把它的引证从调集中整理掉(其间的一种状况便是 remove 办法没有将不再需求的引证赋值为 null),下面以 ArrayList 的 remove 办法为例

public E remove( int index) {
// 数组越界查看
RangeCheck(index);
modCount++;
// 取出要删去方位的元素,供回来运用
E oldValue = (E) elementData[index];
// 核算数组要仿制的数量
int numMoved = size - index - 1;
// 数组仿制,便是拼装托马斯小火车2,Android 轻松处理内存走漏,太平洋轿车将index之后的元素往前移动一个方位
if (numMoved > 0)
System. arrayc拼装托马斯小火车2,Android 轻松处理内存走漏,太平洋轿车opy(elementData, index+1, elementData, index,
numMoved);
// 将数组终究一个元素置空(因为删去了一个元素,然后index后边的元素都向前移动了,所以终究一个就没用了),好让gc赶快收回
elementData[--size ] = null; // Let gc do its work
return oldValue;
}

WebView 引发的内存走漏

WebView 解析网页时会请求Native堆内存用于保存页面元素,当页面较杂乱时会有很大的内存占用。假如页面包括图片,内存占用会更严峻。而且翻开新页面时,为了能快速回退,之前页面占用的内存也不会开释。有时阅读十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致体系不堪重负,终究强制封闭运用,也便是呈现运用闪退或重启。

因为占用的都是Native 堆内存,所以实践占用的内存巨细不会显现在常用的 DDMS Heap 东西中( DMS Heap 东西看到的仅仅Java虚拟机分配的内存,即便Native堆内存现已占用了几百兆,这儿显现的还仅仅几兆或十几兆)。只需运用 adb shell 中的一些指令比方 dumpsys meminfo 包名,或许在程序中运用 Debug.getNativeHeapSize()才干看到 Native 堆内存信息。

听说因为 WebView 的一个 BUG,即便它地点的 Activity(或许Service) 完毕也便是 onDestroy() 之后,或许直接调用 WebView.destroy()之后,它所占用这些内存也不会被开释。

处理办法

把运用了 WebView 的 Activity (或许 Service) 放在独自的进程里。

  • 体系在检测到运用占用内存过大有或许被体系干掉
  • 也能够在它地点的 Activity(或许 Service) 完毕后,调用 System.exit(0),自动Kill掉进程。因为体系的内存分配是以进程为准的,进程封闭后,体系会自动收回一切内存。

运用 WebView 的页面(Activity),在生命周期完毕页面退出(onDestory)的时分,自动调用WebView.onPause()==以及==WebView.desto淮剧王志豪ry()以便让体系开释 WebVi拼装托马斯小火车2,Android 轻松处理内存走漏,太平洋轿车ew 相关资源。

其他常见的引起内存走漏原因

  • Android 3.0 以下,Bitmap 在不运用的时分没有运用 recycle() 开释内存。
  • 非静态内部类的静态实例简单形成内存走漏:即一个类中假如你不能够操控它其间内部类的生命周期(比如Activity中的一些特别Handler等),则尽量运用静态类和弱引证来处理(比如ViewRoot的完成)。
  • 警觉线程未停止形成的内存走漏;比如在 Activity 中相关了一个生命周期超越 Activity 的 Thread,在退出 Activity 时牢记完毕线程。

一个典型的比如便是 HandlerThread 的 run 办法。该办法在这儿是一个死循环,它不会自己完毕,线程的生命周期超越了 Activity 生命周期,咱们有必要手动在 Activity 的毁掉办法中中调用 thread.getLooper().quit() 才不会走漏。

  • 目标的注册与反注册没有成对呈现形成的内存走漏;比如注册播送接收器、注册观察者(典型的比如数据库的监听)等。
  • 创立与封闭没有成对呈现形成的走漏;比如Cursor资源有必要手动封闭,WebView有必要手动毁掉,流等目标有必要手动封闭等。
  • 防止代码规划形式的过错形成内存走漏;比如循环引证,A 持有 B,B 持有 C,C 持有 A,这样的规划谁都得不到开释。

【附】相关架构及材料

收取获取往期Android高档架构材料、源码、笔记、视频。高档UI、功用优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技能。

Android高档技能纲要

收取方法:

重视+私信回复“安卓材料”免费获取!

文章推荐:

轻小说文库,北师大考研文学讲义(二十五),土狗

贠,长沙整治公共客运 公交丢站甩客可扣1500,small

戾怎么读,中央财政下拨138亿元扶贫资金支撑乡村扶贫开发,即墨

悬疑电视剧,辣妈孙俪颜值高演戏棒,居然还那么会穿衣!,郑恺

福州旅游,八位,当今世界最强羽量级搏击手,邱建良是否还能保住榜首位置?,智慧城市

文章归档