OOM 问题分类
-
线程数太多
-
打开太多文件
-
内存不足
线程数太多:
| 报错信息
pthread_create (1040KB stack) failed: Out of memory

查看系统对每个进程的线程数限制:
cat /proc/sys/kernel/threads-max

不同设备的 threads-max 限制是不一样的,有些厂商的低端机型 threads-max 比较小,容易出现此类 OOM 问题
查看当前进程运行的线程数:
cat proc/{pid}/status
当线程数超过 /proc/sys/kernel/threads-max 中规定的上限时就会触发 OOM。
| 线程优化
①禁用 new Thread
解决线程过多问题,传统的方案是禁止使用 new Thread,统一使用线程池,但是一般很难人为控制, 可以在代码提交之后触发自动检测,有问题则通过邮件通知对应开发人员。
不过这种方式存在两个问题:
-
无法解决老代码的 new Thread
-
对于第三方库无法控制
②无侵入性的 new Thread 优化
Java 层的 Thread 只是一个普通的对象,只有调用了 start 方法,才会调用 native 层去创建线程。
所以理论上我们可以自定义 Thread,重写 start 方法,不去启动线程,而是将任务放到线程池中去执行,为了做到无侵入性,需要在编译期通过字节码插桩的方式,将所有 new Thread 字节码都替换成 new 自定义 Thread。
步骤如下:
创建一个 Thread 的子类叫 ShadowThread 吧,重写 start 方法,调用自定义的线程池 CustomThreadPool 来执行任务。
public class ShadowThread extends Thread {
@Override
public synchronized void start() {
Log.i("ShadowThread", "start,name="+ getName());
CustomThreadPool.THREAD_POOL_EXECUTOR.execute(new MyRunnable(getName()));
}
class MyRunnable implements Runnable {
String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
try {
ShadowThread.this.run();
Log.d("ShadowThread","run name="+name);
} catch (Exception e) {
Log.w("ShadowThread","name="+name+",exception:"+ e.getMessage());
RuntimeException exception = new RuntimeException("threadName="+name+",exception:"+ e.getMessage());
exception.setStackTrace(e.getStackTrace());
throw exception;
}
}
}
}
打开太多文件
错误信息
E/art: ashmem_create_region failed for 'indirect ref table': Too many open files
Java.lang.OutOfMemoryError: Could not allocate JNI Env
/proc/pid/limits 描述着 linux 系统对每个进程的一些资源限制
如果没有 root 权限,可以通过 ulimit -n 命令查看 Max open files,结果是一样的。
ulimit -n

内存不足
| 堆栈信息
| 重温 JVM 内存结构
JVM 在运行时,将内存划分为以下 5 个部分:
-
方法区:存放静态变量、常量、即时编译代码
-
程序计数器:线程私有,记录当前执行的代码行数,方便在 cpu 切换到其它线程再回来的时候能够不迷路
-
Java 虚拟机栈:线程私有,一个 Java 方法开始和结束,对应一个栈帧的入栈和出栈,栈帧里面有局部变量表、操作数栈、返回地址、符号引用等信息
-
本地方法栈:线程私有,跟 Java 虚拟机栈的区别在于 这个是针对 native 方法
-
堆:绝大部分对象创建都在堆分配内存
内存不足导致的 OOM,一般都是由于 Java 堆内存不足,绝大部分对象都是在堆中分配内存,除此之外,大数组、以及 Android3.0-7.0 的 Bitmap 像素数据,都是存放在堆中。
Java 堆内存不足导致的 OOM 问题,线上难以复现,往往比较难定位到问题,绝大部分设备都是 8.0 以下的,主要也是由于 Android 3.0-7.0 Bitmap 像素内存是存放在堆中导致的。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战