首先我们提出一个问题, 什么是内存泄漏? 内存泄漏, 通俗得来讲就是 "没有用的对象无法被回收"
然后我们再提出一个问题, 内存泄露会导致什么情况?
肯定是内存溢出, 然后程序崩溃啊!
区别
相信初学者可能不太清楚内存溢出和内存泄漏的区别.
内存溢出: 程序使用的空间大于原本系统给它申请的空间.
内存泄漏: 在 new 了对象之后, 没有使用这个对象了, 但是又没有被回收, 一直占用着内存.
储备知识
要想了解内存泄露的知识, 首先我们要清楚以下的知识点
Java 的 GC(Garbage Collection, 垃圾回收) 机制.
Java 的内存管理机制
详情可以见 浅谈垃圾回收算法
简单判断
如何判断, 只用记住一点: A 类实例引用 B 类实例, 而 A 类实例的生命周期长于 B 类实例的生命周期.
泄漏场景
在 Android 开发中, 内存泄漏的地方还是挺多的, 有时候稍不注意就写出了一个内存泄漏的代码. 所以说我们要熟记哪些地方容易发生内存泄漏, 在代码 Review 的情况下很容易检查出来.
单例引起的内存泄漏
单例模式使用的地方非常多, 它的生命周期常常伴随着 App 的一生, 所以说也十分容易造成内存泄漏.
例如单例模式中引用 Activity 的 Context, 而单例模式的生命周期长于 Activity. 这里单例模式引用 Activity 的实例, 当 Activity 被销毁, Activity 无法被回收, 造成内存泄露.
- public class Single {
- private static Single instance;
- private Context context;
- private Single(Context context) {
- this.context = context;
- }
- public static Single getInstance(Context context) {
- if (instance == null) {
- instance = new Single(context);
- }
- return instance;
- }
- }
值得一提的是, 如果这里引用的 Application 的 Context, 将无任何影响. 因为 Application 的生命周期与单例模式同样长.
集合的内存泄漏
在静态集合里面添加对象, 添加完成之后该集合将会一直引用此对象, 该对象无法被释放.(不过我们也写不出这样沙雕的代码来!
- static List<Object> objectList = new ArrayList<>();
- for (int i = 0; i <10; i++) {
- Object obj = new Object();
- objectList.add(obj);
- obj = null;
- }
解决方法: 在使用完该集合之后, 将集合清空.
匿名内部类以及非静态内部类
特点: 匿名类和非静态内部类都持有外部类的引用
匿名内部类
Handler 泄漏
匿名内部类引起的内存泄露, 最典型的例子就是 Handler 泄漏. 当 Handler 的消息没有发送完毕, Activity 就被销毁了, 此时 Activity 无法被即时回收.
- public class MainActivity extends Activity{
- @SuppressLint("HandlerLeak")
- private Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- //do something...
- }
- };
- @Override
- public void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- }
如何解决 Handler 泄漏呢? 我们用 static 修饰 Handler, 这样 Hanlder 的生命周期就与 Activity 无关了. 如果想引用 Activity 实例, 这里可以用一个弱引用来获取. 或者可以在 Activity 的 onDestroy() 方法中移除所有的消息 handler.removeCallbacksAndMessages(null);
- public class MainActivity extends Activity{
- private final MyHandler handler = new MyHandler(this);
- @Override
- public void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- private static class MyHandler extends Handler {
- private final WeakReference<MainActivity> mActivity;
- public MyHandler(MainActivity activity) {
- mActivity = new WeakReference<MainActivity>(activity);
- }
- @Override
- public void handleMessage(Message msg) {
- MainActivity activity = mActivity.get();
- }
- }
- }
Thread 泄漏
在 Activity 中 new Thread 时, 如果在子线程做耗时操作, 当 Activity 被销毁后, 子线程的工作并未完成, 此时会内存泄漏.
- public class MainActivity extends Activity{
- @Override
- public void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(100000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
- }
这里同样可以继承 Thread 实现静态内部类来解决.
static 修饰的成员变量
如果成员变量被声明为 static, 其生命周期将与整个 App 进程生命周期一样.
Stream 未关闭
在调用了流之后, 一定要记得关闭流. 用到流的地方一般都是文件操作, 虚拟机无法通过垃圾回收来释放这些资源.
其他泄漏
例如 service 忘记解除绑定, broadcastReceiver 忘记解除订阅, EventBus 忘记解除订阅等.
常用的检测内存泄漏的工具
光凭肉眼我们其实只能找出比较明显的内存泄露点, 还有许多隐藏得比较深的内存泄露. 那么我们如何找到这些点呢? 当然是利用工具.
Android Lint:Android Studio 提供的代码扫描分析工具
Leakcanary https://github.com/square/leakcanary : Square 公司开源的「Android 和 Java 的内存泄漏检测库」
总结
在 Android 系统中, 每个 App 最多能分配大约只有 100-200MB 的内存空间, 因为内存不够, 溢出而引起的程序崩溃还是不在少数. 所以说, 日常开发中还是要千万注意内存泄露.
来源: https://juejin.im/post/5c833e49e51d453a637c0af6