踩坑Android 编译线程爆了, gradle 内存 OOM 解决之路
Posted gdutxiaoxu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了踩坑Android 编译线程爆了, gradle 内存 OOM 解决之路相关的知识,希望对你有一定的参考价值。
背景
最近项目在编译,编译多次之后,有挺多人反馈会出现 OOM 的,在项目的根目录下面会出现 hs_err_pid*.log 的错误文件。内容大概如下
这个对我们的开发效率还是有挺大影响的,如果能够解决,对我们的开发效率还是有一定提升的。因此,我们尝试进行解决。
探索原因
从报错的信息来看,‘jar transform Thread’ 有时候的线程数非常多, 很有可能是同时开启的线程数过大,导致内存不足,最终 OOM。
从线程名是 ‘jar transform Thread’ ,根据经验,我们第一时间想到可能是 transform 相关的。
于是,我们找项目当中 transfrom 相关的, 从 buildScan 文件中,找 transfrom 相关的
发现主要有几个
- transformClassesWithRealmTransformerForDebug
- transformClassesWithCom.xx.xx.gradle.plugin.hilt.HiltContextWrapperRemovePluginForDebug
- transformDebugClassesWithAsm
很快我们找到 Hilt, Realm 里面 transform 里面的代码,发现里面的 Thread Name 都不是 jar transform Thread。那应该不是这两个的原因。
讨论之后,我们尝试 dump 编译时 Java 进程的内存信息,看能不能复现?
首先,我们先确定当前进程的 PID
接着我们借助 VisualVM 这个工具,dump 下 JVM 编译时候的进程,编译的时候发现,线程数有时候会越来越多。
于是,我们在想能不能 debug 创建线程的地方,于是,我们在 java.lang.Thread#setName 这里设置条件断点 name.contains("jar transform")
debug gradle assembleDebug 任务,很快我们发现,调用栈关系如下
我们重点关注到了几个跟线程相关的东西
我们跟踪进去,发现这个线程池的核心线程数设置为 2147483647
而上面的线程数不断增多,并且线程名包括 “jar transform”, 那很有可能就是这个线程池了。
我们逐一排查,发现线程池 executor 是在这里传递进来的
跟踪代码,很快我们发现创建改线程池 executor 的地方 DefaultCachedClasspathTransformer#executor
他这里果然没有限制线程的数量。
而我们项目中的 gradle 代码是 6.9.1,于是在想,我们去跟官方最新代码对比一下。
对比官方 gradle 代码
我们首先 clone 官方代码 gradle,找到 DefaultCachedClasspathTransformer,
发现最新代码已经进行了修改,限制了线程的数量。改为跟 CPU 核心数挂钩。
而他是在什么时候进行了修改了,其实很简单,我们可以借助 git 命令,找到他属于哪一个 TAG.
git tag --contains 2a1e74166bc82607e15de78002ef56582b34af0d
很快我们发现了,他在 gradle 7.0 上面对线程池的线程数进行了限制,改为跟 CPU 核心数挂钩。
思考
先来思考一个问题,可能很多人有同样的疑问。
为什么有的机器没出现有的机器会出现,⽐如我同事的mac就没有发现
我么先来看一下 java tranfrom 线程是干什么用的,
我们可以看这里的代码
org.gradle.internal.classpath.DefaultCachedClasspathTransformer.TransformFile#schedule
跟踪下去,你会发现主要是一些 IO 读取操作。
如果说你的机器磁盘性能比较好,那么 IO 读取比较快,线程干完工作之后,就会自动销毁,出现这样现象的可能性会比较低。如果磁盘性能较差,出现的可能性会比较大。
问题解决
既然怀疑问题是因为这里的线程数引起的,于是第一时间我们想到了几种方法
- 反射修改线程池的数量
- 升级 gradle 版本
于是,我们跟中代码,试试反射能不能修改代码,但很快,我们发现,并没有找到一个好的 hook 点,无法修改。
可能有人会想到 epic,没错,刚开始我也想用 epic。但是 epic 是基于安卓 ART 虚拟机的,而我们编译的时候,是基于 JVM 的,epic 是无法使用的。
接着我们尝试了第二种方法,尝试升级 gradle 版本到 7.0,折腾了一fang之后,发现升级要适配的东西还是蛮多的,一下子无法解决
- maven repo 仓库设置 allowInsecureProtocol
- grrovy 版本冲突
- JavaParser 错误
- …
总之,错误是解决完一个接着一个,还是挺多坑的
柳暗花明又一村
跟汉光爷讨论之后,汉光爷说能不能自己编译一个版本出来。他在官网上找到了编译 gradle 版本的方法
编译完成之后,上传到 CC 的 S3 服务器上面,我们在 gradle-wrapper.properties 下面修改,替换成自己的 gradle 版本
#Thu May 30 18:31:45 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
# 解决编译线程数过多,导致 OOM 的问题
distributionUrl=https://xx.cn/static/gradle/gradle-6.9.3-all.zip
再次编译,可以看到 jar transform 相关的线程数,最多变为 8 了,因为我的电脑是 8 核单核心的。
到此,我们对 gradle jar transfrom thread 的线程数进行了限制,合理应该是不会再出现 OOM 了,如果还会出现,可以保留现场,找我或者汉光看看。
总结
可以看到,我们这次的问题解决思路大概是这样的。
- 从 error 日志排查发现,很有可能跟 transfrom 相关
- 排查项目里面 transfrom 相关的,有没有
jar transform Thread
相关的 - dump JVM 内存,看线程相关的,观察
jar transform Thread
是否异常 - debug gradle assemble 任务,观察 线程名包括
jar transform Thread
Thread 的调用堆栈 - 分析 调用堆栈,找到原因
- 结合 gradle 官方代码,查看问题是否已经解决
那有没有更快的方法呢?
其实如果一开始能确定是 gradle 问题的话,可以直接在 gradle 里面搜索字符串 jar transforms
,然后再一步步反推,其实也是可以的。
以上是关于踩坑Android 编译线程爆了, gradle 内存 OOM 解决之路的主要内容,如果未能解决你的问题,请参考以下文章
踩坑Android 编译线程爆了, gradle 内存 OOM 解决之路
Android踩坑小记:ndk版本与Android Gradle Plugin版本兼容
Android踩坑小记:ndk版本与Android Gradle Plugin版本兼容
Android踩坑小记:ndk版本与Android Gradle Plugin版本兼容