泄漏, 泄漏, 漏~
内存泄漏怎么破, 什么是内存泄漏? 与内存溢出有什么区别?
内存泄漏 (Memory Leak): 是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放, 造成系统内存的浪费, 导致程序运行速度减慢甚至系统崩溃等严重后果.
内存溢出 (out of memory): 是指程序在申请内存时, 没有足够的内存空间供其使用, 出现 out of memory;
内存泄漏不一定会引起奔溃, 但是内存溢出一定会
Java 里头有 GC 垃圾回收机制, 他是怎么判断该不该回收呢?
Q1: 某对象没有任何引用的时候才进行回收?
A: 不. 无法往上追溯到 GCroot 引用点的才回收.
Q2: 某对象被别的对象引用就不能进行回收?
A: 不. 软引用, 弱引用, 虚引用都可以
哪些可以作为 GCroot 引用点:
Javastack 中引用的对象
方法区中静态引用指向的对象
方法区中常量引用指向的对象
Native 方法中 JNI 引用指向的对象
Thread - 活着的线程
adb 命令验证是否存在内存泄漏:
1, 打开要测试的 apk, 然后返回退出到主界面
2,AS 的 Terminal 中输入命令:
adb shell dumpsys meminfo com.status.mattest -d
com.status.mattest 为包名
然后便可以看到内存的一些情况
拉到下面可以看到:
我们退出 APK 之后, 对象本应该都被回收, 然而这里可以看到, 还有 view 以及 Activity 占用着内存, 由此可以知道内存泄漏了.
我们点击 AS 上的按钮, 进行分析.
运行 apk 后会出现该界面:
我们点击 MEMORY 进入内存分析界面:
这时候我们需要进行刚刚的操作, 打开 apk, 然后返回键退出, 然后点击上图中垃圾桶形状的图标进行垃圾回收, 多点几次, 直到内存没有什么变化了.
然后点击上图中类似于下载按钮的图标获取内存快照.
获取完之后, 左边会出现下面这图, Head Dump 便是获取内存快照后出现的, 我们可以点击红圈中的图形进行保存
跟着出现的还有这个窗口.
我们可以通过包来分类查看自己的项目.
Shallow Heap(浅堆) 表示该对象自身占用的堆内存, 不包括它引用的对象.
针对非数组类型的对象, 它的大小就是对象与它所有的成员变量大小的总和.
Retained Heap(深堆) 表示当前对象大小 + 当前对象可直接或间接引用到的对象的大小总和.
换句话说, Retained Size 就是当前对象被 GC 后, 从 Heap 上总共能释放掉的内存.
不过, 释放的时候还要排除被 GC Roots 直接或间接引用的对象. 他们暂时不会被被当做 Garbage.
从图中可以看出, 出现了内存泄漏的是 CustomUtils,MainActivity,MainActivity$1 代表 MaiActivity 里面的一个方法.
点击 MainActivity, 可以看到这么对东西, 眼花缭乱, 我们根本不知道是哪个引起了内存泄漏. 那咋办. 铺垫结束, mat 该上场了.
MAT
下载 mat https://www.eclipse.org/mat/downloads.php
安装步骤很简单.
打开 MAT 后, 点击 File -> Open Heap Dump 打开刚刚保存的内存快照, 会发现打不开.
因为我们保存的内存快照是不能直接在 MAT 上打开的, 需要进行转化.
在 AS 的 Terminal 窗口输入: hprof-conv -z A.hprof B.hprof
A.hprof 为刚刚保存后的快照文件, B.hprof 为转换后的文件, 也就是我们要在 MAT 上打开的文件
然后我们在 MAT 上将其打开.
点击 Finish
点击 Histogram
可以通过包名来分类查看
从下图中可以看到引起内存泄漏的类, Objects 那一列不为 0 的就是, 与我们之前看到的一样.
MainActivity 右键
选择图中的选项, 意思是过滤掉虚引用, 软引用, 弱引用
之后可以看到引用的路径, CustomUtils 里面的 instance 引用了 MainActivity 的 context, 导致了 MainActivity 内存泄漏.
打开代码看一下
- package com.status.mattest;
- import Android.content.Context;
- public class CustomUtils {
- private static CustomUtils instance;
- private CustomUtils(Context context) {
- this.mContext = context;
- }
- private Context mContext;
- public static CustomUtils getInstance(Context context) {
- if (instance == null) {
- instance = new CustomUtils(context);
- }
- return instance;
- }
- }
MainActivity 中:
- package com.status.mattest;
- import Android.content.Intent;
- import Android.support.v7.App.AppCompatActivity;
- import Android.os.Bundle;
- import Android.view.View;
- import Android.widget.TextView;
- public class MainActivity extends AppCompatActivity {
- private TextView textView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- CustomUtils customUtils = CustomUtils.getInstance(this);
- textView = findViewById(R.id.tv);
- textView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(new Intent(MainActivity.this, Test1Activity.class));
- }
- });
- }
- }
MainActivity 中调用了单例 CustomUtils, 并且把 context 传了进去, 而我们知道 instance 作为静态对象, 是 GCroot 引用点, 所以 activity 关闭了它也没法被回收, 那么最为被 instance 引用的 Activity 自然也无法被回收, 所以导致了内存泄漏.
解决:
把单例模式里面的 context 换为全局 application 的 context, 也就是说单例里面的 context 的周期应该与进程一样, 而不能够与应用一样. 当我们关闭应用的时候, 进程还在, 并没有被杀死.
来源: https://www.cnblogs.com/tangZH/p/10955429.html