- 线程数太多
- 打开太多文件
- 内存不足
线程数太多:
| 报错信息
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
如果没有 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 像素内存是存放在堆中导致的。