前言

刚开始正式学协程原理的时候(以前只是学api怎么用),大概是20年6月,也就是bennyhuo大佬出书<深入理解Kotlin协程>的时候,我买了本然后细细研究,我的内心就一直有一个问题,协程只挂起不恢复会不会造成协程的内存和程序泄漏.

后来经过我debug和分析jvm字节码,终于找到了答案!

正文

首先放出结论:

  1. 协程只挂起不恢复只会造成一直挂起,后面的代码块得不到执行
  2. 协程只挂起不恢复不会造成协程泄漏(前提是你没有将协程体对象传出去且长时间引用)
  3. 挂起的协程体存在匿名内部类中

ps:主要的分析手段是靠分析jvm字节码,debug算是辅助手段

首先是需要分析的代码:

        main {//启动一个主线程协程"123".e2()//打印日志suspendCoroutine<Unit> { post { it.resume(Unit) } }//挂起并post到主线程之后恢复协程"5".e2()suspendCoroutine<Unit> { }//只挂起不恢复协程"456".e2()}

编译后的jvm字节码再经过反编译得到的java代码(过滤不重要的代码):

启动的代码,封装了launch和scope:

    public void init() {//这里就是上面的入口代码main(this, new MainActivity$init$1((Continuation) null));}public static Job main(BaseActive $this, Function2<? super CoroutineScope, ? super Continuation<? super Unit>, ? extends Object> run) {//下面就是正常的launch流程了return BuildersKt__Builders_commonKt.launch$default($this.getMainScope(), (CoroutineContext) null, (CoroutineStart) null, run, 3, (Object) null);}

挂起函数生成的匿名内部类,对应main方法传入的lambda:

final class MainActivity$init$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {...   public final Object invokeSuspend(Object $result) {MainActivity$init$1 mainActivity$init$1;Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();int i = this.label;if (i == 0) {ResultKt.throwOnFailure($result);mainActivity$init$1 = this;LogUtil.e2$default("123", (String) null, 1, (Object) null);//对应打印日志的方法mainActivity$init$1.L$0 = mainActivity$init$1;mainActivity$init$1.label = 1;//在挂起点将自身这个匿名内部类包一层,创建的一个包装协程体SafeContinuation it = new SafeContinuation(IntrinsicsKt.intercepted(mainActivity$init$1));//这里是对应post方法,这个MainActivity$init$1$1$1就是post方法传入的lambda生成的匿名内部类对象HandlerPoolKt.post$default((String) null, new MainActivity$init$1$1$1(it), 1, (Object) null);Object orThrow = it.getOrThrow();if (orThrow == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {DebugProbesKt.probeCoroutineSuspended(mainActivity$init$1);}if (orThrow == coroutine_suspended) {return coroutine_suspended;}} else if (i == 1) {mainActivity$init$1 = this;MainActivity$init$1 mainActivity$init$12 = (MainActivity$init$1) mainActivity$init$1.L$0;ResultKt.throwOnFailure($result);} else if (i == 2) {MainActivity$init$1 mainActivity$init$13 = (MainActivity$init$1) this.L$0;ResultKt.throwOnFailure($result);LogUtil.e2$default("456", (String) null, 1, (Object) null);return Unit.INSTANCE;} else {throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}LogUtil.e2$default("5", (String) null, 1, (Object) null);mainActivity$init$1.L$0 = mainActivity$init$1;mainActivity$init$1.label = 2;SafeContinuation safeContinuation = new SafeContinuation(IntrinsicsKt.intercepted(mainActivity$init$1));Continuation continuation = safeContinuation;Object orThrow2 = safeContinuation.getOrThrow();if (orThrow2 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {DebugProbesKt.probeCoroutineSuspended(mainActivity$init$1);}if (orThrow2 == coroutine_suspended) {return coroutine_suspended;}MainActivity$init$1 mainActivity$init$14 = mainActivity$init$1;LogUtil.e2$default("456", (String) null, 1, (Object) null);return Unit.INSTANCE;}
}

对应post方法传入的匿名内部类:

final class MainActivity$init$1$1$1 extends Lambda implements Function0<Unit> {final /* synthetic */ Continuation $it;/* JADX INFO: super call moved to the top of the method (can break code semantics) */MainActivity$init$1$1$1(Continuation continuation) {super(0);this.$it = continuation;}public final void invoke() {Continuation continuation = this.$it;Unit unit = Unit.INSTANCE;Result.Companion companion = Result.Companion;continuation.resumeWith(Result.m4constructorimpl(unit));}
}
  1. 在launch后,会经过一系列的协程内部调用,最后会走到MainActivity$init$1的invokeSuspend方法,第一次执行协程代码块时label会是0(label相当于走到了第几步,是通过挂起点来分割步骤的)
  2. 然后走到LogUtil.e2$default("123", (String) null, 1, (Object) null);去打印日志
  3. 接着遇到挂起点,会执行SafeContinuation it = new SafeContinuation(IntrinsicsKt.intercepted(mainActivity$init$1));将自身这个匿名内部类包装一层创建个新的协程体
  4. 然后执行HandlerPoolKt.post$default((String) null, new MainActivity$init$1$1$1(it), 1, (Object) null);,其会创建一个匿名内部类(也就是post的lambda)来在下个时机恢复协程
  5. 这时调用SafeContinuation的getOrThrow方法,会获取到返回值,也就是IntrinsicsKt.getCOROUTINE_SUSPENDED(),表示当前协程会被挂起,然后return了invokeSuspend方法
  6. 在某个时刻主线程相应了post事件,就会执行MainActivity$init$1$1$1对象的invoke方法,invoke方法就会调用匿名内部类的上层对象(jvm匿名内部类的特性是会隐式在构造中传入上层对象)的resume方法来恢复协程
  7. 这时协程会重新调用MainActivity$init$1的invokeSuspend方法,并且label变为了1,在label1里面会检查是否有异常,没有异常就会走到if else后面的代码块,打印日志"5"
  8. 然后在创建一个新的SafeContinuation对象,然后在getOrThrow()会返回IntrinsicsKt.getCOROUTINE_SUSPENDED(),然后协程函数就停止执行了,因为后面没有没有了新的匿名内部类来引用MainActivity$init$1对象,所以其会在调用栈中层层出栈,然后引用链也会断开,所以不会造成内存泄漏,只会造成协程一直挂起,后面的代码块得不到执行

结论

协程只挂起不恢复只会造成一直挂起,后面的代码块得不到执行

上面的几个步骤证明了该结论

挂起的协程体存在匿名内部类中

jvm的匿名内部类的特性之一是会在其隐式的构造函数中传入上层对象,所以协程体的对象会被传入到对应挂起点通过CPS转换得来的匿名内部类对象中,而这个对象如果将来某个时段会调用resume,就会被某个代码节点给引用着(可能是间接的main方法或线程run方法),相当于一层层匿名内部类串联引用保存了协程体的对象

协程只挂起不恢复不会造成协程泄漏

通过上面的代码发现,第二个挂起点处,也就是打印日志"5"的下方,代码只调用了getOrThrow但没有其他时机调用resume,也没有将MainActivity$init$1对象传到其他地方,而jvm内存回收是根据可达性分析算法决定对象可不可以被回收,此时invokeSuspend方法已经执行完毕,MainActivity$init$1对象也已不可达(没有被其他对象引用),该协程就随时有可能被gc给回收掉

本篇文章只是代表个人观察和分析所得,如有错误,欢迎大佬指出.

end

分析Kotlin协程只挂起不恢复会怎样(是否存在协程泄漏),以及挂起的协程存在哪里?相关推荐

  1. Java线程的挂起与恢复 wait(), notify()方法介绍

    一, 什么是线程的挂起与恢复 从字面理解也很简单. 所谓线程挂起就是指暂停线程的执行(阻塞状态). 而恢复时就是让暂停的线程得以继续执行.(返回就绪状态) 二, 为何需要挂起和恢复线程. 我们来看1个 ...

  2. pdf 深入理解kotlin协程_Kotlin协程实现原理:挂起与恢复

    今天我们来聊聊Kotlin的协程Coroutine. 如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine? 如果你已经接触过协程,但对协程的原理存 ...

  3. Kotlin协程:挂起与恢复原理逆向刨析

    前言:只有在那崎岖的小路上不畏艰险奋勇攀登的人,才有希望达到光辉的顶点. --马克思 前言 经过前面两篇协程的学习,我相信大家对协程的使用已经非常熟悉了.本着知其然更要知其之所以然的心态,很想知道它里 ...

  4. 协程的挂起、恢复和调度的原理 (二)

    目录 一. 协程的挂起.恢复和调度的设计思想 二. 深入解析协程 1. 协程的创建与启动 2. 协程的线程调度 3. 协程的挂起和恢复 4. 不同 resumeWith 的解析 5. 协程整体结构 一 ...

  5. 极域脱控破解分析+代码实现(杀死和重启,挂起和恢复,解除全屏按钮限制,获取极域安装路径,极域密码破解)

    免责声明:以下内容仅供学习使用 本文的工具成品下载见githubmythwarehelper仓库,附加资源也在内 已完成功能:杀死极域,获取极域安装路径,从注册表破解极域密码,重启极域,挂起极域,恢复 ...

  6. sonarqube中,分析maven聚合工程时,不必分析parent工程,只需分析下面的module子工程即可

    sonarqube中,分析maven聚合工程时,不必分析parent工程,只需分析下面的module子工程即可 cd ../../xxx-sms # mvn clean org.jacoco:jaco ...

  7. (58)模拟线程切换——添加挂起、恢复线程功能

    一.回顾 我们在上一篇博客分析了模拟线程切换的源码. <模拟线程切换> 我们着重分析了 Scheduling 和 SwitchContext 这两个函数,对线程切换的过程有了新的认识: 线 ...

  8. nuwa :线程分析,nuwa 如何产生,nuwa如何恢复线程?

    nuwa :线程分析,nuwa 如何产生,nuwa如何恢复线程? Nuwa.cpp 的ForkIPCProcess() /**  * Fork a new process that is ready ...

  9. 【STM32】FreeRTOS任务挂起和恢复API

    00. 目录 文章目录 00. 目录 01. 概述 02. vTaskSuspend函数 03. vTaskResume函数 04. xTaskResumeFromISR函数 05. 预留 06. 附 ...

最新文章

  1. C#面向对象(四)虚方法实现多态
  2. spring源码分析之spring-web http详解
  3. 二叉树的层序遍历 使用队列和不使用队列
  4. Interview:算法岗位面试—11.07早上上海某机器人公司(上市)面试之项目考察、比赛考察、图像算法的考察等
  5. 设计模式的理解:单例模式(Singleton)
  6. Day03-运算符和表达式
  7. Golang 反射操作整理
  8. ARP协议详解,ARP协议执行原理、ARP协议如何根据IP地址寻找Mac地址?
  9. 使用CXF实现Webservice的服务接口提供以及相关的客户端实现
  10. 时间区间页面设计两个框html,elementUI 2个输入框 时间区间月份选择
  11. 诺基亚E72_RM-530刷机包023.002版
  12. learun通用权限系统框架功能实现设计
  13. PHP距离高考还剩多少天,今天距离2022年高考还有多少天
  14. 使用PS快速保存多种尺寸的图标
  15. 51单片机控制直流电机
  16. 传说中的程序员最牛表白方式!
  17. kafka 命令、API
  18. HTML_07 —— style属性和样式
  19. 轻松玩转新编日语2 zz江沪
  20. 关于拨号上网的几种错误解决办法

热门文章

  1. NTU 课程笔记: PNP
  2. MATLAB从入门到精通-MATLAB零矩阵eye()函数的几种应用场景
  3. 几种搜索引擎算法的研究
  4. 关于python语言和人工智能哪个说法不正确_在 Windows 7 操作系统中,下列说法错误的是( )。_计算机文化基础答案_学小易找答案...
  5. Python多线程学习教程
  6. Hive SQL基础
  7. 漫谈九品中正制和现阶段阶层分层
  8. 定时任务调度系统设计
  9. /bin/bash^M: bad interpreter: 没有那个文件或目录--转载
  10. Java 编程的动态性,第 6 部分: 利用 Javassist 进行面向方面的更改--转载