• 线程数太多
  • 打开太多文件
  • 内存不足

线程数太多:

| 报错信息

pthread_create (1040KB stack) failed: Out of memory

android pthread_create 库文件_内存不足

 

 

查看系统对每个进程的线程数限制:

cat /proc/sys/kernel/threads-max

android pthread_create 库文件_内存不足_02

 

不同设备的 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

android pthread_create 库文件_Java_03

 

 

内存不足

 

| 堆栈信息

android pthread_create 库文件_自定义_04

 

| 重温 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 像素内存是存放在堆中导致的。