Android 编译线程爆了, gradle 内存 OOM 解决之路
本文首发我的微信公众号徐公,收录于 Github·AndroidGuide,这里有 Android 进阶成长知识体系, 希望我们能够一起学习进步,关注公众号徐公,5 年中大厂程序员,一起建立核心竞争力
背景
最近我们项目在编译的时候,编译多次之后,有挺多人反馈会出现 OOM 的,在项目的根目录下面会出现 hs_err_pid*.log 的错误文件。内容大概如下
这个对我们的开发效率还是有挺大影响的,如果能够解决,对我们的开发效率还是有一定提升的。因此,我们尝试进行解决。
探索原因
从报错的信息来看,'jar transform Thread' 有时候的线程数非常多, 很有可能是同时开启的线程数过大,导致内存不足,最终 OOM。
从线程名是 'jar transform Thread' ,根据经验,我们第一时间想到可能是 transform 相关的。
于是,我们找项目当中 transfrom 相关的, 从 buildScan 文件中,找 transfrom 相关的
发现主要有几个
- transformClassesWithRealmTransformerForDebug
- transformClassesWithCom.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 核心数挂钩。
思考
先来思考一个问题,可能很多人有同样的疑问。
为什么在 windows 上面比较容易出现,在 mac 上面很少出现呢?
java tranfrom 线程是干什么用的,
我们可以看这里的代码
org.gradle.internal.classpath.DefaultCachedClasspathTransformer.TransformFile#schedule
跟踪下去,你会发现主要是一些 IO 读取操作。
如果说你的机器磁盘性能比较好,那么 IO 读取比较快,出现这样现象的可能性会表多。如果磁盘性能较差,出现的可能性会比较大
问题解决
既然怀疑问题是因为这里的线程数引起的,于是第一时间我们想到了几种方法
- 反射修改线程池的数量
- 升级 gradle 版本
于是,我们跟中代码,试试反射能不能修改代码,但很快,我们发现,并没有找到一个好的 hook 点,无法修改。
可能有人会想到 epic,没错,刚开始我也想用 epic。但是 epic 是基于安卓 ART 虚拟机的,而我们编译的时候,是基于 JVM 的,epic 是无法使用的。
接着我们尝试了第二种方法,尝试升级 gradle 版本到 7.0,折腾了一笔之后,发现升级要适配的东西还是蛮多的,一下子无法解决
- maven repo 仓库设置 allowInsecureProtocol
- grrovy 版本冲突
- JavaParser 错误
- ......
总之,错误是解决完一个接着一个,还是挺多坑的
柳暗花明又一村
跟同事讨论之后,说能不能自己编译一个版本出来。于是我们在官网上找到了编译 gradle 版本的方法
编译完成之后,我们在 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
# 解决 编译线程数过多
distributionUrl=https://xx.cn/static/gradle/gradle-6.9.3-all.zip
再次编译,可以看到 jar transform 相关的线程数,最多变为 8 了,跟我们 CPU 数量一样
我们对 gradle jar transfrom thread 的线程数进行了限制,经过
总结
可以看到,我们这次的问题解决思路大概是这样的。
- 从 error 日志排查发现,很有可能跟 transfrom 相关
- 排查项目里面 transfrom 相关的,有没有
jar transform Thread相关的 - dump JVM 内存,看线程相关的,观察
jar transform Thread是否异常 - debug gradle assemble 任务,观察 线程名包括
jar transform ThreadThread 的调用堆栈 - 分析 调用堆栈,找到原因
- 结合 gradle 官方代码,查看问题是否已经解决
那有没有更快的方法呢?
其实如果一开始能确定是 gradle 问题的话,可以直接在 gradle 里面搜索字符串 jar transforms,然后再一步步反推,其实也是可以的。
推荐阅读
耗时一周,我解决了微信 Matrix 增量编译的 Bug,已提 PR
自定义 Hook,解决 RxJava 异常时堆栈信息显示不全
Android 启动优化(二) - 拓扑排序的原理以及解题思路
Android 启动优化(三)- AnchorTask 开源了
Android 启动优化(四)- AnchorTask 是怎么实现的
Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了
Android 编译线程爆了, gradle 内存 OOM 解决之路的更多相关文章
- android编译时出现org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:compileDebugJavaWithJavac'.错误
android studio中使用terminal工具.在android studio最下面的底部菜单栏中有(如果没有那cmd中进入项目根目录后): 使用命令 gradlew compileDebu ...
- Android提升Gradle编译速度或减少Gradle编译时间.md
目录 Android如何提升Gradle编译速度或减少Gradle编译时间 最终优化方案 优化效果比对 将所有项目源码,各种缓存临时目录都移动到高性能SSD磁盘上 gradle.properties ...
- Android Studio编译的时候提示Gradle无法下载的解决方案
首先,打开android studio项目 找到项目目录gradle\wrapper\gradle-wrapper.properties这个文件.内容如下:#Wed Apr 10 15:27:10 P ...
- Android编译优化系列-kapt篇
作者:字节跳动终端技术---王龙海 封光 兰军健 一.背景 本文是编译优化系列文章之 kapt 优化篇,后续还会有 build cache, kotlin, dex 优化等文章,敬请期待.本文由Cli ...
- Android开发之深入理解Android Studio构建文件build.gradle配置
摘要: 每周一次,深入学习Android教程,TeachCourse今天带来的一篇关于Android Studio构建文件build.gradle的相关配置,重点学习几个方面的内容:1.applica ...
- Android内存优化解决 资料和总结的经验分享
在前公司做一个图片处理的应用时, 项目交付的时候,客户的手机在运行应用的时候,一直在崩溃,而这个异常就是OutOfMemory的错误,简称为OOM, 搞得我们也是极其的崩溃,最后 ,我们是通过网上搜集 ...
- [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程
本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...
- 为 Android 编译并集成 FFmpeg 的尝试与踩坑
前言与环境说明 随着 FFmpeg.NDK 与 Android Studio 的不断迭代,本文可能也会像我参考过的过期文章一样失效(很遗憾),但希望本文中提到的问题排查以及步骤说明能够帮到你,如果发现 ...
- [转]Android Studio系列教程六--Gradle多渠道打包
转自:http://www.stormzhang.com/devtools/2015/01/15/android-studio-tutorial6/ Android Studio系列教程六--Grad ...
- android 进程/线程管理(四)续----消息机制的思考(自定义消息机制)
继续分析handler 和looper 先看看handler的 public void dispatchMessage(Message msg) { if (msg.callback != null) ...
随机推荐
- java固定窗口大小
this.setResizable(false);//////frame.setResizable(false)
- Educational Codeforces Round 102 (Rated for Div. 2) A~D题
写在前边 链接:Educational Codeforces Round 102 (Rated for Div. 2) 第一场打CF,过了AB两题,不过速度挺慢,C题属实没看懂,D题写了个常规做法之后 ...
- Mariadb 10.4 root 用户无法使用无密码登录的原因
Mariadb现在是网站建设中使用的主流数据库之一,当前它很多个版本:10.1.10.2.10.3.10.4. 其中10.1和10.2在程序中使用root用户登录连接数据库都没有什么问题. 如果是10 ...
- 滚动更新和回滚部署在 Kubernetes 中的工作原理
公众号「架构成长指南」,专注于生产实践.云原生.分布式系统.大数据技术分享. 在过去的几年中,Kubernetes 在生产环境中被广泛使用,它通过其声明式 API 提供了大量解决方案,用于编排容器. ...
- C#不用正则校验密码(附赠正则表达式)
群聊 群友A问:有没有方法可以判断字符串里面带不带标点符号啊? 群友B问:需求是什么? 群友A说:想要密码校验,网上大部分都是正则. 群友A说:密码规则是包含大小写和数字.特殊符号,还有Length& ...
- Git使用(GitEE)
Git分布式版本控制工具 1. Git概述 1.1 Git历史 Git 诞生于一个极富纷争大举创新的年代.Linux 内核开源项目有着为数众多的参与者. 绝大多数的 Linux 内核维护工作都花在了提 ...
- 微调baichuan2-7b遇到的显存坑
问题描述: 微调baichuan2-7b模型,验证一轮后继续训练第一个iteration显存大幅增加 项目链接: https://github.com/wp931120/baichuan_sft_lo ...
- Scrapy如何在启动时向爬虫传递参数
高级方法: 一般方法: 运行爬虫时使用-a传递参数 scrapy crawl 爬虫名 -a key=values 然后在爬虫类的__init__魔法方法中获取kwargs class Bang123S ...
- MySQL运维16-双主双从读写分离
一.双主双从架构介绍 在MySQL多主多从的架构配置中和双主双从是一样的,学会了双主双从的架构部署,多主多从的配置也同样就回了.下面以双主双从作为示例演示.其中一个主机maste1用于处理所有写请求, ...
- 关于RichEdit的那些坑
项目开发中用到了richedit,但是并没有用到图文的功能,只是说使用他的各种属性,集成了一个自己的超文本编辑器. 开发遇到了各种坑,在这里跟大家分享下: 1: 跳转编辑界面,无法获取焦点. 通过Ri ...