内存泄漏是当有程序再也不使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并非指物理上的内存消失,这里的内存泄漏是指由程序分配的内存,因为程序逻辑错误而致使程序失去了对该内存的控制,使得内存浪费。java
Java 程序运行时的内存分配策略有三种,分别是 静态分配
、 栈式分配
和 堆式分配
,对应的三种存储策略使用的内存空间主要分别是 静态存储区(也称方法区)
、 栈区
和 堆区
。android
🔰 静态存储区(方法区):主要存放 静态数据
、 全局 static 数据
和 常量
。这块内存在程序编译时就已经分配好,而且在程序整个运行期间都存在。程序员
🔰 栈区:当方法被执行时,方法体内的 局部变量
(其中包括基础数据类型、对象的引用)都在栈上建立,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。由于栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。web
🔰 堆区: 又称动态内存分配,一般就是指在程序运行时直接 new 出来的内存,也就是 对象的实例
。这部份内存在不使用时将会由 Java 垃圾回收器(GC)来负责回收。算法
在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的做用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间能够被从新使用。编程
堆内存用来存放全部由 new 建立的对象(包括该对象其中的全部成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还能够在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是咱们上面说的引用变量。咱们能够经过这个引用变量来访问堆中的对象或者数组。api
举例说明:数组
public class Sample { int s1 = 0; Sample mSample1 = new Sample(); public void method() { int s2 = 1; // Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中, // 但 mSample2 指向的对象是存在于堆上 Sample mSample2 = new Sample(); } } // mSample3 指向的对象实体存放在堆上,包括这个对象的全部成员变量 s1 和 mSample1, // 而它本身存在于栈中。 Sample mSample3 = new Sample();
Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员须要经过关键字 new 为每一个对象申请内存空间 (基本类型除外),全部的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工做。但同时,它也加剧了JVM的工做。这也是 Java 程序运行速度较慢的缘由之一。由于,GC 为了可以正确释放对象,GC 必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都须要进行监控。网络
监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象再也不被引用。架构
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特色,首先,这些对象是可达的,即在有向图中,存在通路能够与其相连;其次,这些对象是无用的,即程序之后不会再使用这些对象。若是对象知足这两个条件,这些对象就能够断定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,而后却不可达,因为C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,所以程序员不须要考虑这部分的内存泄露。
经过分析,咱们得知,对于C++,程序员须要本身管理边和顶点,而对于Java程序员只须要管理边就能够了(不须要管理顶点的释放)。经过这种方式,Java提升了编程的效率。
所以,经过以上分析,咱们知道在Java中也有内存泄漏,但范围比C++要小一些。由于Java从语言上保证,任何对象都是可达的,全部的不可达对象都由GC管理。
对于程序员来讲,GC基本是透明的,不可见的。虽然,咱们只有几个函数能够访问GC,例如运行GC的函数System.gc(),可是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器必定会执行。由于,不一样的JVM实现者可能使用不一样的算法管理GC。一般,GC的线程的优先级别较低。JVM调用GC的策略也有不少种,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但一般来讲,咱们不须要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不但愿GC忽然中断应用程序执行而进行垃圾回收,那么咱们须要调整GC的参数,让GC可以经过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。
如下给出一个 Java 内存泄漏的典型例子:
Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }
在这个例子中,咱们循环申请Object对象,并将所申请的对象放入一个 Vector 中,若是咱们仅仅释放引用自己,那么 Vector 仍然引用该对象,因此这个对象对 GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。
单例的使用在咱们的程序中随处可见,由于使用它能够完美的解决咱们在程序中重复建立对象的问题,不过可别小瞧它。因为 单例的静态特性使得其生命周期跟应用的生命周期同样长 ,因此一旦使用有误,当心无限制的持有Activity的引用而致使内存泄漏。
咱们看个例子:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } }
这是一个普通的单例模式,当建立这个单例的时候,因为须要传入一个Context,因此这个Context的生命周期的长短相当重要! (实际常见)
一、若是此时传入的是 Application 的 Context ,由于 Application 的生命周期就是整个应用的生命周期,因此这将没有任何问题。
二、若是此时传入的是 Activity 的 Context ,当这个 Context 所对应的 Activity 退出时,因为该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,因此当前 Activity 退出时它的内存并不会被回收,这就形成泄漏了。
正确的方式(写法一):
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext(); // 使用 Application 的 context } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } }
正确的方式(写法二):
// 在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context ... context = getApplicationContext(); ... /** * 获取全局的context * @return 返回全局context对象 */ public static Context getContext(){ return context; } public class AppManager { private static AppManager instance; private Context context; private AppManager() { this.context = MyApplication.getContext(); // 使用Application 的context } public static AppManager getInstance() { if (instance == null) { instance = new AppManager(); } return instance; } }
咱们看一段代码:
public class MainActivity extends AppCompatActivity { private static MainActivity activity; // 这边设置了静态Activity,发生了内存泄漏 TextView saButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saButton = (TextView) findViewById(R.id.text); saButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticActivity(); nextActivity(); } }); } void setStaticActivity() { activity = this; } void nextActivity(){ startActivity(new Intent(this,RegisterActivity.class)); SystemClock.sleep(1000); finish(); } @Override protected void onDestroy() { super.onDestroy(); } }
在上面代码中,咱们声明了一个静态的 Activity 变量而且在 TextView 的 OnClick 事件里引用了当前正在运行的 Activity 实例,因此若是在 activity 的生命周期结束以前没有清除这个引用,则会引发内存泄漏。由于声明的 activity 是静态的,会常驻内存,若是该对象不清除,则垃圾回收器没法回收变量。
咱们能够这样解决:
protected void onDestroy() { super.onDestroy(); activity = null; // 在onDestory方法中将静态变量activity置空,这样垃圾回收器就能够将静态变量回收 }
其实和静态Activity颇为类似,咱们看下代码:
... private static View view; // 定义静态View TextView saButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); saButton = (TextView) findViewById(R.id.text); saButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticView(); nextActivity(); } }); } void setStaticView() { view = findViewById(R.id.sv_view); } ...
View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是咱们的Activity,声明一个静态变量引用这个View,也就引用了activity,因此当activity生命周期结束了,静态View没有清除掉,还持有activity的引用,所以内存泄漏了。
咱们能够这样解决:
protected void onDestroy() { super.onDestroy(); view = null; // 在onDestroy方法里将静态变量置空 }
咱们看下面的例子:
public class MainActivity extends AppCompatActivity { void startAsyncTask() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { while(true); } }.execute(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View aicButton = findViewById(R.id.at_button); aicButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask(); } }); } }
上面代码在activity中建立了一个匿名类 AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,所以若是你在 Activity 里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,若是这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。
咱们能够这样解决:
自定义静态 AsyncTask 类,而且让 AsyncTask 的周期和 Activity 周期保持一致,也就是在 Activity 生命周期结束时要将 AsyncTask cancel 掉。
有的时候咱们可能会在启动频繁的Activity中,为了不重复建立相同的数据资源,可能会出现这种写法:
public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mManager == null){ mManager = new TestResource(); } //... } class TestResource { //... } }
上面这段代码在Activity内部建立了一个非静态内部类的单例(mManager),每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复建立,不过这种写法却会形成内存泄漏。
由于非静态内部类默认会持有外部类的引用,而该非静态内部类又建立了一个静态的实例,该实例的生命周期和应用的同样长,这就致使了该静态实例一直会持有该Activity的引用,致使Activity的内存资源不能正常回收。
正确的作法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,若是须要使用Context,请按照上面推荐的使用Application 的 Context。固然,Application 的 context 不是万能的,因此也不能随便乱用,对于有些地方则必须使用 Activity 的 Context。
Handler 的使用形成的内存泄漏问题应该说是 最为常见 了,不少时候咱们为了不 ANR 而不在主线程进行耗时操做,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写不规范即有可能形成内存泄漏。另外,咱们知道 Handler、Message 和 MessageQueue 都是相互关联在一块儿的,万一 Handler 发送的 Message 还没有被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
因为 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。所以这种实现方式通常很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易致使没法正确释放。
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
在该 SampleActivity 中声明了一个延迟 10分钟 执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,因此此时 finish() 掉的 Activity 就不会被回收了从而形成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。
正确的作法为:
在 Activity 中避免使用非静态内部类,好比上面咱们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时经过弱引用的方式引入 Activity,避免直接将 Activity 做为 context 传进去,见下面代码:
public class SampleActivity extends Activity { private static class MyHandler extends Handler { private final WeakReference<SampleActivity> mActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference<SampleActivity>(activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { // 每次使用前注意判空 // ... } } } private final MyHandler mHandler = new MyHandler(this); private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
从上面的代码中咱们能够看出如何避免Handler内存泄漏,推荐使用 "静态内部类 + WeakReference" 这种方式,每次使用前注意判空。
Java对引用的分类有Strong reference、SoftReference、WeakReference、PhatomReference四种。
级别 | 回收机制 | 用途 | 生存时间 |
---|---|---|---|
强 | 历来不会 | 对象的通常状态 | JVM中止运行时终止 |
软 | 在内存不足时 | 联合ReferenceQueue构造有效期短/占内存打/生命周期长的对象的二级高速缓冲器(内存不足时才状况) | 内存不足时终止 |
弱 | 在垃圾回收时 | 联合ReferenceQueue构造有效期短/占内存打/生命周期长的对象的一级高速缓冲器(系统发生gc时清空) | gc运行后终止 |
虚 | 在垃圾回收时 | 联合ReferenceQueue来跟踪对象被垃圾回收期回收的活动 | gc运行后终止 |
在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大并且声明周期较长的对象时候,能够尽可能应用软引用和弱引用技术。
软/弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列能够得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。
看个范例:
public class SampleActivity extends Activity { void spawnThread() { new Thread() { @Override public void run() { while(true); } }.start(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); View tButton = findViewById(R.id.t_button); tButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { spawnThread(); } }); } }
其实这边发生的内存泄漏缘由跟AsyncTask是同样的。
正确的作法为:
咱们自定义Thread并声明成static这样能够吗?其实这样的作法并不推荐,由于Thread位于GC根部,DVM会和全部的活动线程保持hard references关系,因此运行中的Thread毫不会被GC无故回收了,因此正确的解决办法是在自定义静态内部类的基础上给线程加上取消机制,所以咱们能够在Activity的onDestroy方法中将thread关闭掉。
看个范例:
public class SampleActivity extends Activity { void scheduleTimer() { new Timer().schedule(new TimerTask() { @Override public void run() { while(true); } },1000); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); View ttButton = findViewById(R.id.tt_button); ttButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scheduleTimer(); } }); } }
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而致使Timer和TimerTask一直引用外部类Activity。
正确的作法为:
在适当的时机进行Cancel。
看个范例:
public class SampleActivity extends Activity { void registerListener() { SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); View smButton = findViewById(R.id.sm_button); smButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { registerListener(); } }); } }
经过Context调用getSystemService获取系统服务,这些服务运行在他们本身的进程执行一系列后台工做或者提供和硬件交互的接口,若是Context对象须要在一个Service内部事件发生时随时收到通知,则须要把本身做为一个监听器注册进去,这样服务就会持有一个Activity,若是开发者忘记了在Activity被销毁前注销这个监听器,这样就致使内存泄漏。
正确的作法为:
在onDestroy方法里注销监听器。
若是成员变量被声明为 static,那咱们都知道其生命周期将与整个app进程生命周期同样。
这会致使一系列问题,若是你的app进程设计上是长驻内存的,那即便app切到后台,这部份内存也不会被释放。按照如今手机app内存管理机制,占内存较大的后台进程将优先回收,若是此app作过进程互保保活,那会形成app在后台频繁重启。当手机安装了你参与开发的app之后一晚上时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。
这里修复的方法是:
不要在类初始时初始化静态成员。能够考虑lazy初始化(使用时初始化)。架构设计上要思考是否真的有必要这样作,尽可能避免。若是架构须要这么设计,那么此对象的生命周期你有责任管理起来。
一、finalize 方法被执行的时间不肯定,不能依赖与它来释放紧缺的资源。时间不肯定的缘由是:
虚拟机调用GC的时间不肯定
Finalize daemon线程被调度到的时间不肯定
二、finalize 方法只会被执行一次,即便对象被复活,若是已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,缘由是:
含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即便在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候因为没有了 finalize reference 与之对应,因此 finalize 方法不会再执行。
三、含有Finalize方法的object须要至少通过两轮GC才有可能被释放。
咱们一般会把一些对象的引用加入到集合容器(好比ArrayList)中,当咱们再也不须要该对象时,并无把它的引用从集合中清理掉,这样这个集合就会愈来愈大。若是这个集合是static的话,那状况就更严重了。
因此在退出程序以前,将集合里面的东西clear,而后置为null,再退出程序,以下:
private List<String> nameList; private List<Fragment> list; @Override public void onDestroy() { super.onDestroy(); if (nameList != null){ nameList.clear(); nameList = null; } if (list != null){ list.clear(); list = null; } }
当咱们再也不须要使用webView的时候,应该调用它的destory()方法来销毁它,并释放其占用的内存,不然其占用的内存长期也不能回收,从而形成内存泄漏。
正确的作法为:
为webView开启另一个进程,经过AIDL与主线程进行通讯,webView所在的进程能够根据业务的须要选择合适的时机进行销毁,从而达到内存的完整释放。
对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,不然这些资源将不会被回收,形成内存泄漏。
若是在程序中使用static声明属性,则此属性称为全局属性(也称静态属性),那么声明成全局属性有什么用?咱们看下代码:
class Person { String name; int age; static String country = "A城"; public Person(String name, int age) { this.name = name; this.age = age; } public void info() { System.out.println("姓名:" + this.name + ",年龄:" + this.age + ",城市:" + country); } }; public class Demo { public static void main(String agrs[]) { Person p1 = new Person("张三", 30); Person p1 = new Person("李四", 31); Person p1 = new Person("王五", 32); Person.country = "B城"; p1.info(); p2.info(); p3.info(); } }
以上程序很清晰的说明了static声明属性的好处,须要注意一点的是,类的公共属性应该由类进行修改是最合适的(固然也能够p1.country = ...),有时也就把使用static声明的属性称为类属性。
直接看下代码就清楚了:
class Person { private String name; private int age; private static String country = "A城"; public static void setCountry(String C) { country = c; } public Person(String name, int age) { this.name = name; this.age = age; } public void info() { System.out.println("姓名:" + this.name + ",年龄:" + this.age + ",城市:" + country); } public static String getCountry() { return country; } }; public class Demo { public static void main(String agrs[]) { Person p1 = new Person("张三", 30); Person p1 = new Person("李四", 31); Person p1 = new Person("王五", 32); Person.setCountry("B城"); p1.info(); p2.info(); p3.info(); } }
【特殊说明】
💥 非static声明的方法能够调用static声明的属性或方法
💥 static声明的方法不能调用非static声明的属性或方法
好比如下代码就会出错:
class Person { private static String country = "A城"; private String name = "Hello"; public static void sFun(String C) { System.out.println("name = " + name); // 错误,不能调用非static属性 fun(); // 错误,不能调用非static方法 } public void fun() { System.out.println("World!!!"); } };
咱们都知道,在类内部能够定义成员变量与方法,一样,在类内部也能够定义另外一个类。若是在类Outer的内部定义一个类Inner,此时类Inner就称为内部类,而类Outer则称为外部类。
内部类可声明成 public 或 private。当内部类声明成 public 或 private时,对其访问的限制与成员变量和成员方法彻底相同。
标识符 class 外部类的名称 { // 外部类的成员 标识符 class 内部类的名称 { // 内部类的成员 } }
能够方便地访问外部类中的私有属性!
使用static能够声明属性或方法,而使用static也能够声明内部类,用static声明的内部类就变成了外部类,可是用static声明的内部类不能访问非static的外部类属性。
好比以下例子:
class Outer { private static String info = "Hello World!!!"; // 若是此时info不是static属性,则程序运行报错 static class Inner { public void print() { System.out.println(info); } }; }; public class InnerClassDemo { public static void main(String args[]) { new Outer.Inner().print(); } }
执行结果:
Hello World!!!
一个内部类除了能够经过外部类访问,也能够直接在其余类中进行调用。
【在外部访问内部类的格式】
外部类.内部类 内部类对象 = 外部类实例.new 内部类();
class Outer { private String info = "Hello World!!!"; class Inner { public void print() { System.out.println(info); } }; }; public class InnerClassDemo { public static void main(String args[]) { Outer out = new Out(); // 实例化外部类对象 Outer.Inner in = out.new Inner(); // 实例化内部类对象 in.print(); // 调用内部类方法 } }
除了在外部类中定义内部类,咱们也能够在方法中定义内部类。可是须要注意的是,在方法中定义的内部类不能直接访问方法中的参数,若是方法中的参数想要被内部类访问,则参数前必须加上final关键字。
class Outer { private String info = "Hello World!!!"; public void fun(final int temp) { // 参数要被访问必须用final声明 class Inner { public void print() { System.out.println("类中的属性:" + info); System.out.println("方法中的参数:" + temp); } }; new Inner().print(); } }; public class InnerClassDemo { public static void main(String args[]) { new Outer().fun(30); // 调用外部类方法 } }
在开发中,内存泄漏最坏的状况是app耗尽内存致使崩溃,可是每每真实状况不是这样的,相反它只会耗尽大量内存但不至于闪退,可分配的内存少了,GC便会更多的工做释放内存,GC是很是耗时的操做,所以会使得页面卡顿。咱们在开发中必定要注意当在Activity里实例化一个对象时看看是否有潜在的内存泄漏,必定要常常对内存泄漏进行检测。
01. https://developer.android.goo...
02. https://developer.android.goo...
03. https://www.jianshu.com/p/f77...