分析Kotlin协程只挂起不恢复会怎样(是否存在协程泄漏),以及挂起的协程存在哪里?
前言
剛開始正式學協程原理的時候(以前只是學api怎么用),大概是20年6月,也就是bennyhuo大佬出書<深入理解Kotlin協程>的時候,我買了本然后細細研究,我的內心就一直有一個問題,協程只掛起不恢復會不會造成協程的內存和程序泄漏.
后來經過我debug和分析jvm字節碼,終于找到了答案!
正文
首先放出結論:
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));} }結論
協程只掛起不恢復只會造成一直掛起,后面的代碼塊得不到執行
上面的幾個步驟證明了該結論
掛起的協程體存在匿名內部類中
jvm的匿名內部類的特性之一是會在其隱式的構造函數中傳入上層對象,所以協程體的對象會被傳入到對應掛起點通過CPS轉換得來的匿名內部類對象中,而這個對象如果將來某個時段會調用resume,就會被某個代碼節點給引用著(可能是間接的main方法或線程run方法),相當于一層層匿名內部類串聯引用保存了協程體的對象
協程只掛起不恢復不會造成協程泄漏
通過上面的代碼發現,第二個掛起點處,也就是打印日志"5"的下方,代碼只調用了getOrThrow但沒有其他時機調用resume,也沒有將MainActivity$init$1對象傳到其他地方,而jvm內存回收是根據可達性分析算法決定對象可不可以被回收,此時invokeSuspend方法已經執行完畢,MainActivity$init$1對象也已不可達(沒有被其他對象引用),該協程就隨時有可能被gc給回收掉
?
本篇文章只是代表個人觀察和分析所得,如有錯誤,歡迎大佬指出.
end
?
?
總結
以上是生活随笔為你收集整理的分析Kotlin协程只挂起不恢复会怎样(是否存在协程泄漏),以及挂起的协程存在哪里?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 动手实现Kotlin协程同步切换线程,以
- 下一篇: 使用Retrofit的方式请求Socke