Java内存回收方式

Java判断对象是否可以回收使用的而是可达性分析算法。

在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

在Java语言里,可作为GC Roots对象的包括如下几种:

    a.虚拟机栈(栈桢中的本地变量表)中的引用的对象b.方法区中的类静态属性引用的对象c.方法区中的常量引用的对象d.本地方法栈中JNI的引用的对象
复制代码

摘自《深入理解Java虚拟机》

使用leakcanary检测泄漏

关于LeakCanary使用参考以下文章:

  1. LeakCanary: 让内存泄露无所遁形
  2. LeakCanary 中文使用说明

LeakCanary的内存泄露提示一般会包含三个部分: 第一部分(LeakSingle类的sInstance变量) 引用第二部分(LeakSingle类的mContext变量), 导致第三部分(MainActivity类的实例instance)泄露.

leakcanary使用注意

即使是空的Activity,如果检测泄露时候遇到了如下这样的泄露,注意,把refWatcher.watct()放在onDestroy里面即可解决,或者忽略这样的提示。 由于文章已写很多,下面的就不再修改,忽略这种错误即可。

* com.less.demo.TestActivity has leaked:
* GC ROOT static android.app.ActivityThread.sCurrentActivityThread
* references android.app.ActivityThread.mActivities
* references android.util.ArrayMap.mArray
* references array java.lang.Object[].[1]
* references android.app.ActivityThread$ActivityClientRecord.activity
* leaks com.less.demo.TestActivity instance
复制代码
protected void onDestroy() {super.onDestroy();RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);
}
复制代码

leakcanary和代码示例说明内存泄露

案例一(静态成员引起的内存泄露)

public class App extends Application {private RefWatcher refWatcher;@Overridepublic void onCreate() {super.onCreate();refWatcher = LeakCanary.install(this);}public static RefWatcher getRefWatcher(Context context) {App application = (App) context.getApplicationContext();return application.refWatcher;}
}复制代码

测试内部类持有外部类引用,内部类是静态的(GC-ROOT,将一直连着这个外部类实例),静态的会和Application一个生命周期,这会导致一直持有外部类引用(内部类隐含了一个成员变量$0), 即使外部类制空= null,也无法释放。

OutterClass

public class OutterClass {private String name;class Inner{public void list(){System.out.println("outter name is " + name);}}
}
复制代码

TestActivity

public class TestActivity extends Activity {// 静态的内部类实例private static OutterClass.Inner innerClass;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);OutterClass outterClass = new OutterClass();innerClass = outterClass.new Inner();RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(outterClass);// 监控的对象outterClass = null;}复制代码

案例二(单例模式引起的内存泄露)

DownloadManager

public class DownloadManager {private static DownloadManager instance;private Task task ;public static DownloadManager getInstance(){if (instance == null) {instance = new DownloadManager();}return instance;}public Task newTask(){this.task = new Task();return task;}
}
复制代码

Task

public class Task {private Call call;public Call newCall(){this.call = new Call();return call;}
}复制代码

Call

public class Call {public void execute(){System.out.println("=========> execute call");}
}复制代码

TestActivity

public class TestActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);RefWatcher refWatcher = App.getRefWatcher(this);Task task = DownloadManager.getInstance().newTask();Call call = task.newCall();call.execute();refWatcher.watch(call);// 监控的对象call = null; // 无法回收,DownloadManager是静态单例,引用task,task引用了call,即使call置为空,也无法回收,切断GC_ROOT 联系即可避免内存泄露,即置task为空。}
}
复制代码

部分日志打印如下:当前的GC_ROOT是DownloadManager的instance实例。

 In com.leakcanary.demo:1.0:1.
* com.less.demo.Call has leaked:
* GC ROOT static com.less.demo.DownloadManager.instance
* references com.less.demo.DownloadManager.task
* references com.less.demo.Task.call
* leaks com.less.demo.Call instance
复制代码

关于上面两种方式导致的内存泄露问题,这里再举两个案例说明以加强理解。

案例三(静态变量导致的内存泄露)

public class TestActivity extends Activity {private static Context sContext;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);sContext = this;RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);// 监控的对象}复制代码

打印日志如下:

 In com.leakcanary.demo:1.0:1.
com.less.demo.TestActivity has leaked:
GC ROOT static com.less.demo.TestActivity.sContext
leaks com.less.demo.TestActivity instance
复制代码

从这段日志可以分析出:声明static后,sContext的生命周期将和Application一样长,Activity即使退出到桌面,Application依然存在->sContext依然存在,GC此时想回收Activity却发现Activity仍然被sContext(GC-ROOT连接着),导致死活回收不了,内存泄露。

上面的代码改造一下,如下。

public class TestActivity extends Activity {private static View sView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);sView = new View(this);RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);}
}
复制代码

日志如下

In com.leakcanary.demo:1.0:1.
com.less.demo.TestActivity has leaked:
GC ROOT static com.less.demo.TestActivity.sView
references android.view.View.mContext
leaks com.less.demo.TestActivity instance
复制代码

案例四(单例模式导致的内存泄露) DownloadManager

public class DownloadManager {private static DownloadManager instance;private List<DownloadListener> mListeners = new ArrayList<>();public interface DownloadListener {void done();}public static DownloadManager getInstance(){if (instance == null) {instance = new DownloadManager();}return instance;}public void register(DownloadListener downloadListener){if (!mListeners.contains(downloadListener)) {mListeners.add(downloadListener);}}public void unregister(DownloadListener downloadListener){if (mListeners.contains(downloadListener)) {mListeners.remove(downloadListener);}}
}
复制代码

TestActivity

public class TestActivity extends Activity implements DownloadManager.DownloadListener{@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);DownloadManager.getInstance().register(this);RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);}@Overrideprotected void onDestroy() {super.onDestroy();// 忘记 unregister// DownloadManager.getInstance().unregister(this);}@Overridepublic void done() {System.out.println("done!");}
}
复制代码
 In com.leakcanary.demo:1.0:1.
* com.less.demo.TestActivity has leaked:
* GC ROOT static com.less.demo.DownloadManager.instance
* references com.less.demo.DownloadManager.mListeners
* references java.util.ArrayList.array
* references array java.lang.Object[].[0]
* leaks com.less.demo.TestActivity instance
复制代码

错误写法一定导致内存泄露吗?

答案是:否定的。 如下案例,有限时间内是可以挽救内存泄露发生的,所以控制好生命周期,其他情况:如单例对象(静态变量)的生命周期比其持有的sContext 的生命周期更长时 ->内存泄露,更短时->躲过内存泄露。

public class TestActivity extends Activity {private static Context sContext;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);sContext = this;new Handler().postDelayed(new Runnable() {@Overridepublic void run() {sContext = null; }},1000);// 分别测试1s和12s,前者不会内存泄露,后者一定泄露。所以如果赶在GC之前切断GC_ROOT是可以避免内存泄露的。}@Overrideprotected void onDestroy() {super.onDestroy();RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);}
}
复制代码

Handler 引起的内存泄露详细分析

handler导致的内存泄露可能我们大多数都犯过. 注意以下代码中的注释,非静态内部类虽然持有外部类引用,但是持有并不代表一定泄露,而是看gc时谁的命长。经过测试 **情况(1)**始终没有内存泄露。

为什么会这样, 很早阅读Handler源码时候记得这几个货都是互相引用来引用去的,Message有个target字段, message.target = handler; handler.post(message);又把这个message推入了MessageQueue中,而MessageQueue是在一个Looper线程中不断轮询处理消息,而有时候message还是个老不死,能够重复利用。如果当Activity退出时候,还有消息未处理或正在处理,由于message引用handler,handler又引用Activity,此时将引发内存泄露。

public class TestActivity extends Activity {private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);System.out.println("===== handle message ====");}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);// (1) 不会导致内存泄露handler.sendEmptyMessageDelayed(0x123,0);// (2) 会导致内存泄露,非静态内部类(包括匿名内部类,比如这个 Handler 匿名内部类)会引用外部类对象 this(比如 Activity)// 当它使用了 postDelayed 的时候,如果 Activity 已经 finish 了,而这个 handler 仍然引用着这个 Activity 就会致使内存泄漏// 因为这个 handler 会在一段时间内继续被 main Looper 持有,导致引用仍然存在.handler.sendEmptyMessageDelayed(0x123, 12000);}@Overrideprotected void onDestroy() {super.onDestroy();RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);}
}
复制代码
com.less.demo.TestActivity has leaked:
* GC ROOT android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper.mH
* references com.android.internal.view.IInputConnectionWrapper$MyHandler.mQueue
* references android.os.MessageQueue.mMessages
* references android.os.Message.target , matching exclusion field android.os.Message#target
* references com.less.demo.TestActivity$1.this$0 (anonymous subclass of android.os.Handler)
* leaks com.less.demo.TestActivity instance
复制代码

知道了原理后,即使写出易于内存泄露的代码也能够避免内存泄露啦。 上述代码只需在onDestroy()函数中调用mHandler.removeCallbacksAndMessages(null); 在Activity退出的时候的移除消息队列中所有消息和所有的Runnable。

内部类引起的内存泄露

内部类种类大致如下:

  1. 非静态内部类(成员内部类)
  2. 静态内部类(嵌套内部类)
  3. 局部内部类(定义在方法内或者作用域内的类,好似局部变量,所以不能有访问控制符和static等修饰)
  4. 匿名内部类(没有名字,仅使用一次new个对象即扔掉类的定义)

为什么非静态内部类持有外部类引用,静态内部类不持有外部引用。

这个问题非常简单,就像 static的方法只能调用static的东西,非static可以调用非static和static的一样。static--> 针对class, 非static->针对 对象,我是这么简单理解的。看图:

匿名内部类 将局部内部类的使用再深入一步,假如只创建某个局部内部类的一个对象,就不必命名了。

匿名内部类的类型可以是如下几种方式。

  1. 接口匿名内部类
  2. 抽象类匿名内部类
  3. 类匿名内部类

匿名内部类总结:

  1. 其实主要就是类定义一次就失效了,主要使用的是这个类(不知道名字)的实例。根据内部类的特性,能够实现回调和闭包。
  2. JavaScript和Python的回调传递的是fuction,Java传递的是object。 Java中常常用到回调,而回调的本质就是传递一个对象,JavaScript或其他语言则是传递一个函数(如Python,或者C,使用函数指针的方式),由于传递一个对象可以携带其他的一些信息,所以Java认为传递一个对象比传递一个函数要灵活的多(当然java也可以用Method反射对象传递函数)。参考《Java核心技术》

非静态内部类导致内存泄露(前提dog的命长) 下面的案例将导致内存泄露 其中private static Dog dog ; 导致Dog的命比TestActivity长,这就糟糕了,但是注意,如果改为private Dog dog ; 即使Dog是非静态内部类,也不会导致内存泄露,要死也是Dog先死,毕竟Dog是TestActivity的家庭成员,开挂也得看主人。

public class TestActivity extends Activity {private static Dog dog ;class Dog {public void say(){System.out.println("I am lovely dog!");}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);dog = new Dog();}@Overrideprotected void onDestroy() {super.onDestroy();RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);}
}
复制代码
In com.leakcanary.demo:1.0:1.
* com.less.demo.TestActivity has leaked:
* GC ROOT static com.less.demo.TestActivity.dog
* references com.less.demo.TestActivity$Dog.this$0
* leaks com.less.demo.TestActivity instance
复制代码

哪些内部类或者回调函数是否持有外部类对象? 一个反射案例说明一切

/*** 作者: limitless* 描述: 一个案例测试所有类型内部类对外部类对象的持有情况* 网站: https://github.com/wangli0*/
public class Main {/* 持有外部类引用 */private IAppListener mAppListener = new IAppListener() {private String name;@Overridepublic void done() {System.out.println("匿名内部类对象作为成员变量");}};/* 未持有 */private static IAppListener sAppListener = new IAppListener() {private String name;@Overridepublic void done() {System.out.println("匿名内部类对象作为static成员变量");}};public static void main(String args[]) {Main main = new Main();main.test1();main.test2();main.test3();// test3 《=》test4main.test4();main.test5();main.test6();}class Dog {private String name;}/* 持有外部类引用 */public void test1(){Dog dog = new Dog();getAllFieldName(dog.getClass());}static class Cat {private String name;}/* 未持有 */private void test2() {Cat cat = new Cat();getAllFieldName(cat.getClass());}/* 持有外部类引用 */private void test3() {class Monkey{String name;}Monkey monkey = new Monkey();getAllFieldName(monkey.getClass());}/* 持有外部类引用 */private void test4() {// 常用作事件回调的地方(有可能引起内存泄露)IAppListener iAppListener = new IAppListener() {private String name;@Overridepublic void done() {System.out.println("匿名内部类");}};getAllFieldName(iAppListener.getClass());}/* 持有外部类引用 */private void test5() {getAllFieldName(mAppListener.getClass());}/* 未持有 */private void test6() {getAllFieldName(sAppListener.getClass());}private void getAllFieldName(Class<?> clazz) {System.out.println("className: ======> " + clazz.getSimpleName());Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {System.out.println(field.getName());}}
}复制代码

上述结果足够说明,即使是方法内的回调对象也是持有外部类引用的,那么虽然作用域是局部的,也有存在内存泄露的可能。我多次强调 持有某对象 不代表一定泄露,看的是谁命长。回调在Android开发过程中几乎处处可见,如果持有就会内存泄露的话,那几乎就没法玩了。 一般情况下,我们常常设置某个方法内的xx.execute(new Listener(){xx});是不会导致内存泄露的,这个方法执行完,局部作用域就失效了。但是如果在execute(listener);过程中,某个单例模式的对象 突然引用了这个listener对象,那么就会导致内存泄露。

下面用实例证明我的想法 TestActivity

public class TestActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);Task task = new Task();task.execute(new ICallback() {@Overridepublic void done() {System.out.println("下载完成!");}});}@Overrideprotected void onDestroy() {super.onDestroy();RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);}
}复制代码

Task

public class Task {public void execute(ICallback iCallback) {DownloadManager.getInstance().execute(iCallback);}
}
复制代码

DownloadManager

public class DownloadManager {public static DownloadManager instance;private ICallback mICallback;public static DownloadManager getInstance(){if (instance == null) {instance = new DownloadManager();}return instance;}public void execute(ICallback iCallback) {this.mICallback = iCallback;// 反例,千万不要这么做,将导致内存泄露,如果注释掉这行,内存泄露不会发生iCallback.done();}
复制代码

这足以证明我的想法是正确的,Callback的不巧当使用同样会导致内存泄露 的发送。

总结

  1. 如果某些单例需要使用到Context对象,推荐使用Application的context,不要使用Activity的context,否则容易导致内存泄露。单例对象的生命周期和Application一致,这样Application和单例对象就一起销毁。
  2. 优先使用静态内部类而不是非静态的,因为非静态内部类持有外部类引用可能导致垃圾回收失败。如果你的静态内部类需要宿主Activity的引用来执行某些东西,你要将这个引用封装在一个WeakReference中,避免意外导致Activity泄露,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收 只被弱引用关联 的对象,只被 说明这个对象本身已经没有用处了。
public class TestActivity extends Activity {private MyHandler myHandler = new MyHandler(this);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);}static class MyHandler extends Handler {private WeakReference<Activity> mWeakReference;public MyHandler(Activity activity){mWeakReference = new WeakReference<Activity>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Toast.makeText(mWeakReference.get(), "xxxx", Toast.LENGTH_LONG).show();Log.d("xx", mWeakReference.get().getPackageName());}}@Overrideprotected void onDestroy() {super.onDestroy();RefWatcher refWatcher = App.getRefWatcher(this);refWatcher.watch(this);}
}
复制代码

彻底搞懂Java内存泄露相关推荐

  1. python 单例模式内存泄露_彻底搞懂Java内存泄露

    之前一直在简书写作,第一次发布到SF上来,也是第一次使用SF,后面会尽量同步到SF,更多文章请关注: 简书 编程之乐 转载请注明出处:谢谢! Java内存回收方式 Java判断对象是否可以回收使用的而 ...

  2. JVM - 结合代码示例彻底搞懂Java内存区域_对象在堆-栈-方法区(元空间)之间的关系

    文章目录 Pre 示例demo 总体关系 代码示例论证 反汇编 Pre JVM - 结合代码示例彻底搞懂Java内存区域_线程栈 | 本地方法栈 | 程序计数器 中我们探讨了线程栈中的内部结构 ,大家 ...

  3. 全方位带你彻底搞懂Android内存泄露

    1 Java内存回收方式 Java判断对象是否可以回收使用的而是可达性分析算法. 在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的.这个算法的基本思路就是通过一系列名 ...

  4. JVM - 结合代码示例彻底搞懂Java内存区域_线程栈 | 本地方法栈 | 程序计数器

    文章目录 Pre 运行时数据区总览 线程栈 概要 栈内部主要组成部分 局部变量 操作数栈 动态链接 方法出口 小结 程序计数器 本地方法栈 附 测试demo javap JVM字节码指令集手册 Pre ...

  5. Java内存泄露系列--内存泄露的原因及解决方案(大全)

    原文网址:Java内存泄露系列--内存泄露的原因及解决方案(大全)_IT利刃出鞘的博客-CSDN博客 简介 简介 本文介绍Java中内存泄露的一些原因与解决方案. 如果内存泄露的空间足够大,就会导致内 ...

  6. 5张图搞懂Java深浅拷贝

    微信搜一搜 「bigsai」 关注这个专注于Java和数据结构与算法的铁铁 文章收录在github/bigsai-algorithm 欢迎star收藏 如果本篇对你有帮助,记得点赞收藏哦! 在开发.刷 ...

  7. java 内部类定于_搞懂 JAVA 内部类

    前些天写了一篇关于 2018 年奋斗计划的文章,其实做 Android 开发也有一段时间了,文章中所写的内容,也都是在日常开发中遇到各种问题后总结下来需要巩固的基础或者进阶知识.那么本文就从内部类开刀 ...

  8. 搞懂 Java equals 和 hashCode 方法

    搞懂 Java equals 和 hashCode 方法 分析完 Java List 容器的源码后,本来想直接进入 Set 和 Map 容器的源码分析,但是对于这两种容器,内部存储元素的方式的都是以键 ...

  9. ChatGPT似乎有的时候并不能搞懂Java的动态分派,你懂了吗?

    目录 碎碎念 ChatGPT 中出现的问题 那么正确答案应该是什么呢? 分派的相关知识点总结: 分派是什么? 静态分派与动态分派: Java语言是静态多分派,动态单分派的: 静态分派:静态重载多分派: ...

最新文章

  1. 电脑编程教学_“人工智能”将无处不在,我的孩子要不要学习电脑编程?
  2. 笔记本高分屏字体模糊_高色域+高分辨率轻薄本推荐,你需要2K屏笔记本电脑么?...
  3. Redis 面试题 50 问,史上最全
  4. 飞桨第六课 2020.4.5
  5. 怎么清理文件缓存文件云服务器,服务器运行内存怎么清理缓存
  6. 为什么要学python-为什么要学 Python?
  7. java中的@override
  8. 终于要来了!华为P50将提供两个版本:国内仅有鸿蒙
  9. windows启动linux系统,windows 10 启动linux系统
  10. 为系统加载右键注册控件选项【VB 注册控件】
  11. EAST实现自然场景下文本检测tensorflow
  12. AngularJs的基础——$http请求数据
  13. 【转】android开发必看资源URL
  14. MFC Windows 程序设计[二十一]之树形控件
  15. SDN(软件定义网络)简史-早期
  16. 最新kali之medusa
  17. 百钱买小鸡/*公鸡5文钱1只,母鸡三文钱一只,小鸡一文钱三只。现在用100文钱共买了100只鸡,问这100只鸡中,公鸡,母鸡,小鸡各是多少只?
  18. FEC【筷云早报】2020年3月16日星期一
  19. 安装与破解IntelliJ IDEA2017
  20. android HID添加(三) ---applist key

热门文章

  1. 全球人工智能战略与政策观察(2019)
  2. 一句话总结贝叶斯分类器
  3. 两虎相争将带来优质的互联网搜索服务 --- 我看Google归来!
  4. SAP ABAP 如何查询一个变量表里的变量被哪支程序使用到?
  5. SAP ABAP FM AC_DOCUMENT_RECORD 研习
  6. 强化学习之gym初战实战案例:悬崖案例CliffWalking-v0。
  7. 华为自动驾驶实车实路测试视频曝光!
  8. Nature展示迄今为止最详细的“人脑零部件清单”
  9. 专设AI周会 高管悉数到场 微软CEO有多重视人工智能?
  10. DeepMind开源Psychlab平台——搭建AI和认知心理学的桥梁(附论文和代码下载)