本文首发我的微信公众号徐公,收录于 Github·AndroidGuide,这里有 Android 进阶成长知识体系, 希望我们能够一起学习进步,关注公众号徐公,5 年中大厂程序员,一起建立核心竞争力

背景

最近我们项目在编译的时候,编译多次之后,有挺多人反馈会出现 OOM 的,在项目的根目录下面会出现 hs_err_pid*.log 的错误文件。内容大概如下

这个对我们的开发效率还是有挺大影响的,如果能够解决,对我们的开发效率还是有一定提升的。因此,我们尝试进行解决。

探索原因

从报错的信息来看,'jar transform Thread' 有时候的线程数非常多, 很有可能是同时开启的线程数过大,导致内存不足,最终 OOM。

从线程名是 'jar transform Thread' ,根据经验,我们第一时间想到可能是 transform 相关的。

于是,我们找项目当中 transfrom 相关的, 从 buildScan 文件中,找 transfrom 相关的

发现主要有几个

  1. transformClassesWithRealmTransformerForDebug
  2. transformClassesWithCom.xx.gradle.plugin.hilt.HiltContextWrapperRemovePluginForDebug
  3. 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 读取比较快,出现这样现象的可能性会表多。如果磁盘性能较差,出现的可能性会比较大

问题解决

既然怀疑问题是因为这里的线程数引起的,于是第一时间我们想到了几种方法

  1. 反射修改线程池的数量
  2. 升级 gradle 版本

于是,我们跟中代码,试试反射能不能修改代码,但很快,我们发现,并没有找到一个好的 hook 点,无法修改。

可能有人会想到 epic,没错,刚开始我也想用 epic。但是 epic 是基于安卓 ART 虚拟机的,而我们编译的时候,是基于 JVM 的,epic 是无法使用的。

接着我们尝试了第二种方法,尝试升级 gradle 版本到 7.0,折腾了一笔之后,发现升级要适配的东西还是蛮多的,一下子无法解决

  1. maven repo 仓库设置 allowInsecureProtocol
  2. grrovy 版本冲突
  3. JavaParser 错误
  4. ......

总之,错误是解决完一个接着一个,还是挺多坑的

柳暗花明又一村

跟同事讨论之后,说能不能自己编译一个版本出来。于是我们在官网上找到了编译 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 的线程数进行了限制,经过

总结

可以看到,我们这次的问题解决思路大概是这样的。

  1. 从 error 日志排查发现,很有可能跟 transfrom 相关
  2. 排查项目里面 transfrom 相关的,有没有 jar transform Thread 相关的
  3. dump JVM 内存,看线程相关的,观察 jar transform Thread 是否异常
  4. debug gradle assemble 任务,观察 线程名包括 jar transform Thread Thread 的调用堆栈
  5. 分析 调用堆栈,找到原因
  6. 结合 gradle 官方代码,查看问题是否已经解决

那有没有更快的方法呢?

其实如果一开始能确定是 gradle 问题的话,可以直接在 gradle 里面搜索字符串 jar transforms,然后再一步步反推,其实也是可以的。

推荐阅读

耗时一周,我解决了微信 Matrix 增量编译的 Bug,已提 PR

自定义 Hook,解决 RxJava 异常时堆栈信息显示不全

Android 启动优化(一) - 有向无环图

Android 启动优化(二) - 拓扑排序的原理以及解题思路

Android 启动优化(三)- AnchorTask 开源了

Android 启动优化(四)- AnchorTask 是怎么实现的

Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了

Android 启动优化(六)- 深入理解布局优化

Android 编译线程爆了, gradle 内存 OOM 解决之路的更多相关文章

  1. android编译时出现org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:compileDebugJavaWithJavac'.错误

    android studio中使用terminal工具.在android studio最下面的底部菜单栏中有(如果没有那cmd中进入项目根目录后): 使用命令  gradlew compileDebu ...

  2. Android提升Gradle编译速度或减少Gradle编译时间.md

    目录 Android如何提升Gradle编译速度或减少Gradle编译时间 最终优化方案 优化效果比对 将所有项目源码,各种缓存临时目录都移动到高性能SSD磁盘上 gradle.properties ...

  3. Android Studio编译的时候提示Gradle无法下载的解决方案

    首先,打开android studio项目 找到项目目录gradle\wrapper\gradle-wrapper.properties这个文件.内容如下:#Wed Apr 10 15:27:10 P ...

  4. Android编译优化系列-kapt篇

    作者:字节跳动终端技术---王龙海 封光 兰军健 一.背景 本文是编译优化系列文章之 kapt 优化篇,后续还会有 build cache, kotlin, dex 优化等文章,敬请期待.本文由Cli ...

  5. Android开发之深入理解Android Studio构建文件build.gradle配置

    摘要: 每周一次,深入学习Android教程,TeachCourse今天带来的一篇关于Android Studio构建文件build.gradle的相关配置,重点学习几个方面的内容:1.applica ...

  6. Android内存优化解决 资料和总结的经验分享

    在前公司做一个图片处理的应用时, 项目交付的时候,客户的手机在运行应用的时候,一直在崩溃,而这个异常就是OutOfMemory的错误,简称为OOM, 搞得我们也是极其的崩溃,最后 ,我们是通过网上搜集 ...

  7. [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程

    本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...

  8. 为 Android 编译并集成 FFmpeg 的尝试与踩坑

    前言与环境说明 随着 FFmpeg.NDK 与 Android Studio 的不断迭代,本文可能也会像我参考过的过期文章一样失效(很遗憾),但希望本文中提到的问题排查以及步骤说明能够帮到你,如果发现 ...

  9. [转]Android Studio系列教程六--Gradle多渠道打包

    转自:http://www.stormzhang.com/devtools/2015/01/15/android-studio-tutorial6/ Android Studio系列教程六--Grad ...

  10. android 进程/线程管理(四)续----消息机制的思考(自定义消息机制)

    继续分析handler 和looper 先看看handler的 public void dispatchMessage(Message msg) { if (msg.callback != null) ...

随机推荐

  1. C语言输入一行字符,分别统计出其中英文字母、空格、数字与其它字符得个数。

    #include<stdio.h> void main() { char c; int letter = 0, space = 0, digit = 0, other = 0; print ...

  2. Ubuntu下使用apt-get命令查询并安装指定版本的软件

    执行以下命令,查询软件所有的版本号 sudo apt-cache madison <package> <package>为需要安装的包名,返回结果第二列即可用的版本号 执行以下 ...

  3. New Type Functions/Utilities for Dealing with Ranges in C++20

    Generic Types of Ranges   类型萃取从字面意思上来说其实就是帮助我们挑选某个对象的类型,筛选特定的对象来做特定的事.可以先来回顾一下以前的写法. #include <ve ...

  4. 什么是oa软件?oa软件能做什么?

    什么是OA软件? OA软件是办公自动化软件(Office Automation Software)的简称,是一种通过计算机技术,对办公业务流程进行管理和自动化处理的软件系统.它主要用于协调和管理企业内 ...

  5. 虚拟机运行Hadoop | 各种问题解决的心路历程

    ps:完成大数据技术实验报告的过程,出项各种稀奇古怪的问题.(知道这叫什么吗?经济基础决定上层建筑,我当时配置可能留下了一堆隐患,总之如果有同样的问题,希望可以帮到你) 一.虚拟机网络连接不通的各种情 ...

  6. cocos2d-Js 各类碰撞检测

    这里总结一下点.圆.矩形之间的简单碰撞检测算法(矩形不包括旋转状态) 点和圆的碰撞检测: 1.计算点和圆心的距离 2.判断点与圆心的距离是否小于圆的半径 isCollision: function(p ...

  7. SpringBoot项目整合微信登录

    一.开通微信登录 去微信开发者平台 1.注册 2.邮箱激活 3.完善开发者资料 4.开发者资质认证 准备营业执照,1-2个工作日审批.300元 5.创建网站应用 6.提交审核,7个工作日审批 7.熟悉 ...

  8. Feign源码解析:初始化过程(一)

    前言 打算系统分析下Feign的代码,上一篇讲了下Feign的历史,本篇的话,先讲下Feign相关的beanDefinition,beanDefinition就是bean的设计图,bean都是按照be ...

  9. Kernel Memory 入门系列:文档的管理

    Kernel Memory 入门系列: 文档的管理 在Quick Start中我们了解到如何快速直接地上传文档.当时实际中,往往会面临更多的问题,例如文档如何更新,如何划定查询范围等等.这里我们将详细 ...

  10. 专业的字节码查看工具——jclasslib

    下载地址: GitHub地址:点击下方蓝色链接即可. https://github.com/ingokegel/jclasslib/releases/download/6.0.4/jclasslib_ ...