【性能优化】大厂OOM优化和监控方案

大意了,又是 OOM ,Android 内存监控一定要注意这几点! 前言 性能优化是一个老生常谈的问题,也一直是Android中高级攻城狮进阶和深入的方向。 在性能优化的各种问题中,内存泄漏一直占有较重要的地位。内存泄漏的积累会造成内存溢出,进而导致程序崩溃,出现OOM问题。 针对这个问题,我们需要在上线前进行检测同时也要能够实现线上的监控。 方案一:线下性能监控 Android Studio自身提供的MAT等工具分析内存泄漏比较耗时,因而诞生了LeakCanary——这也是现在寻找内存泄漏问题最常用的工具。 LeakCanary 这个工具非常简单,依赖 Lea 阅读详情

一、前言

随着项目不断壮大,OOM(Out Of Memory)成为奔溃统计平台上的疑难杂症之一,大部分业务开发人员对于线上OOM问题一般都是暂不处理,一方面是因为OOM问题没有足够的log,无法在短期内分析解决,另一方面可能是忙于业务迭代、身心疲惫,没有精力去研究OOM的解决方案。

这篇文章将以线上OOM问题作为切入点,介绍常见的OOM类型、OOM的原理、大厂OOM优化黑科技、以及主流的OOM监控方案。

文章较长,请备好小板凳~

二、OOM问题分类

很多人对于OOM的理解就是Java虚拟机内存不足,但通过线上OOM问题分析,OOM可以大致归为以下3类:

  1. 线程数太多
  2. 打开太多文件
  3. 内存不足

接下来将分别围绕这三类问题进行展开分析~

三、线程数太多

3.1 报错信息

pthread_create (1040KB stack) failed: Out of memory

这个是典型的创建新线程触发的OOM问题

oom1.png

3.2 源码分析

pthread_create触发的OOM异常,源码(Android 9)位置如下: androidxref.com/9.0.0_r3/xr…

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  ...
  pthread_create_result = pthread_create(...)
  //创建线程成功
  if (pthread_create_result == 0) {
      return;
  }
  //创建线程失败
  ...
  {
    std::string msg(child_jni_env_ext.get() == nullptr ?
        StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
        StringPrintf("pthread_create (%s stack) failed: %s",
                                 PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
    ScopedObjectAccess soa(env);
    soa.Self()->ThrowOutOfMemoryError(msg.c_str());
  }
}

pthread_create里面会调用Linux内核创建线程,那什么情况下会创建线程失败呢?

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

cat /proc/sys/kernel/threads-max

线程数限制.png

不同设备的threads-max限制是不一样的,有些厂商的低端机型threads-max比较小,容易出现此类OOM问题。

查看当前进程运行的线程数

cat proc/{pid}/status

线程数.png

当线程数超过/proc/sys/kernel/threads-max中规定的上限时就会触发OOM。

既然系统对每个进程的线程数有限制,那么解决这个问题的关键就是尽可能降低线程数的峰值。

3.3 线程优化

3.3.1 禁用 new Thread

解决线程过多问题,传统的方案是禁止使用new Thread,统一使用线程池,但是一般很难人为控制, 可以在代码提交之后触发自动检测,有问题则通过邮件通知对应开发人员。

不过这种方式存在两个问题:

  1. 无法解决老代码的new Thread;
  2. 对于第三方库无法控制。
3.3.2 无侵入性的new Thread 优化

Java层的Thread只是一个普通的对象,只有调用了start方法,才会调用native 层去创建线程,

所以理论上我们可以自定义Thread,重写start方法,不去启动线程,而是将任务放到线程池中去执行,为了做到无侵入性,需要在编译期通过字节码插桩的方式,将所有new Thread字节码都替换成new 自定义Thread。

步骤如下:

1、创建一个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;
            }
        }
    }
}

2、在编译期,hook 所有new Thread字节码,全部替换成我们自定义的ShadowThread,这个难度应该不大,按部就班,

我们先确认new Thread和new ShadowThread对应字节码差异,可以安装一个ASM Bytecode Viewer插件,如下所示

image.png

通过字节码修改,你可以简单理解为做如下替换:

image.png

3、由于将任务放到线程池去执行,假如线程奔溃了,我们不知道是哪个线程出问题,所以自定义ShadowThread中的内部类MyRunnable 的作用是:在线程出现异常的时候,将异常捕获,还原它的名字,重新抛出一个信息更全的异常。

测试代码

    private fun testThreadCrash() {
        Thread {
            val i = 9 / 0
        }.apply {
            name = "testThreadCrash"
        }.start()
    }

开启一个线程,然后触发奔溃,堆栈信息如下:

线程堆栈.png

可以看到原本的new Thread已经被优化成了CustomThreadPool线程池调用,并且奔溃的时候不用担心找不到线程是哪里创建的,会还原线程名。

当然这种方式有一个小问题,应用正常运行的情况下,如果你想要收集所有线程信息,那么线程名可能不太准确,因为通过new Thread 去创建线程,已经被替换成线程池调用了,获取到的线程名是线程池中的线程的名字

数据对比

同个场景简单测试了一下new Thread优化前后线程数峰值对比:

线程数峰值(优化前)线程数峰值(优化后)降低最大线程数
33731423

对于不同App,优化效果会有一些不同,不过可以看到这个优化确实是有效的。

3.3.3 无侵入的线程池优化

随着项目引入的SDK越来越多,绝大部分SDK内部都会使用自己的线程池做异步操作,

线程池的参数如果设置不对,核心线程空闲的时候没有释放,会使整体的线程数量处于较高位置。

线程池几个参数:
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

  1. corePoolSize:核心线程数量。核心线程默认情况下即使空闲也不会释放,除非设置allowCoreThreadTimeOut为true。
  2. maximumPoolSize:最大线程数量。任务数量超过核心线程数,就会将任务放到队列中,队列满了,就会启动非核心线程执行任务,线程数超过这个限制就会走拒绝策略;
  3. keepAliveTime:空闲线程存活时间
  4. unit:时间单位
  5. workQueue:队列。任务数量超过核心线程数,就会将任务放到这个队列中,直到队列满,就开启新线程,执行队列第一个任务。
  6. threadFactory:线程工厂。实现new Thread方法创建线程
通过线程池参数,我们可以找到优化点如下:
  1. 限制空闲线程存活时间,keepAliveTime 设置小一点,例如1-3s;
  2. 允许核心线程在空闲时自动销毁
executor.allowCoreThreadTimeOut(true)

如何做呢?为了做到无侵入性,依然采用ASM操作字节码,跟new Thread的替换基本同理

在编译期,通过ASM,做如下几个操作:
  1. 将调用 Executors 类的静态方法替换为自定义 ShadowExecutors 的静态方法,设置executor.allowCoreThreadTimeOut(true);
  2. 将调用 ThreadPoolExecutor 类的构造方法替换为自定义 ShadowThreadPoolExecutor 的静态方法,设置executor.allowCoreThreadTimeOut(true);
  3. 可以在 Application 类的 () 中调用我们自定义的静态方法 ShadowAsyncTask.optimizeAsyncTaskExecutor() 来修改 AsyncTask 的线程池参数,调用executor.allowCoreThreadTimeOut(true);

你可以简单理解为做如下替换: image.png

3.4 线程监控

假如线程优化后还存在创建线程OOM问题,那我们就需要监控是否存在线程泄漏的情况。

3.4.1 线程泄漏监控

主要监控native线程的几个生命周期方法:pthread_create、pthread_detach、pthread_join、pthread_exit。

  1. hook 以上几个方法,用于记录线程的生命周期和堆栈,名称等信息;
  2. 当发现一个joinable的线程在没有detach或者join的情况下,执行了pthread_exit,则记录下泄露线程信息;
  3. 在合适的时机,上报线程泄露信息。

linux线程中,pthread有两种状态joinable状态和unjoinable状态。joinable状态下,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符。只有当你调用了pthread_join之后这些资源才会被释放,需要main函数或者其他线程去调用pthread_join函数。

3.4.2 线程上报

当监控到线程有异常的时候,我们可以收集线程信息,上报到后台进行分析。

收集线程信息代码如下:

    private fun dumpThreadIfNeed() {

        val threadNames = runCatching { File("/proc/self/task").listFiles() }
            .getOrElse {
                return@getOrElse emptyArray()
            }
            ?.map {
                runCatching { File(it, "comm").readText() }.getOrElse { "failed to read $it/comm" }
            }
            ?.map {
                if (it.endsWith("\n")) it.substring(0, it.length - 1) else it
            }
            ?: emptyList()

        Log.d("TAG", "dumpThread = " + threadNames.joinToString(separator = ","))
    }

接下来介绍打开太多文件导致的OOM问题

四、打开太多文件

4.1 错误信息

E/art: ashmem_create_region failed for 'indirect ref table': Too many open files
Java.lang.OutOfMemoryError: Could not allocate JNI Env

这个问题跟系统、厂商关系比较大

4.2 系统限制

Android是基于Linux内核,/proc/pid/limits 描述着linux系统对每个进程的一些资源限制,

如下图是一台Android 6.0的设备,Max open files的限制是1024

maxopenfiles.png

如果没有root权限,可以通过ulimit -n命令查看Max open files,结果是一样的

ulimit -n

ulimit.png

Linux 系统一切皆文件,进程每打开一个文件就会产生一个文件描述符fd(记录在/proc/pid/fd下面)

cd /proc/10654/fd

ls

fd.png

这些fd文件都是链接文件,通过 ls -l可以查看其对应的真实文件路径

fd2.png

当fd的数目达到Max open files规定的数目,就会触发Too many open files的奔溃,这种奔溃在低端机上比较容易复现。

知道了文件描述符这玩意后,看看怎么优化~

4.2 文件描述符优化

对于打开文件数太多的问题,盲目优化其实无从下手,总体的方案是监控为主。

通过如下代码可以查看当前进程的fd信息

    private fun dumpFd() {
        val fdNames = runCatching { File("/proc/self/fd").listFiles() }
            .getOrElse {
                return@getOrElse emptyArray()
            }
            ?.map { file ->
                runCatching { Os.readlink(file.path) }.getOrElse { "failed to read link ${file.path}" }
            }
            ?: emptyList()

        Log.d("TAG", "dumpFd: size=${fdNames.size},fdNames=$fdNames")

    }

4.3 文件描述符监控

监控策略: 当fd数大于1000个,或者fd连续递增超过50个,就触发fd收集,将fd对应的文件路径上报到后台。

这里模拟一个bug,打开一个文件多次不关闭,通过dumpFd,可以看到很多重复的文件名,进而大致定位到问题。

fd count.png

当怀疑某个文件有问题之后,我们还需要知道这个文件在哪创建,是谁创建的,这个就涉及到IO监控~

4.4 IO监控

4.4.1 监控内容

监控完整的IO操作,包括open、read、write、close

open:获取文件名、fd、文件大小、堆栈、线程

read/write:获取文件类型、读写次数、总大小,使用buffer大小、读写总耗时

close:打开文件总耗时、最大连续读写时间

4.4.2 Java监控方案:

以Android 6.0 源码为例,FileInputStream 的调用链如下

java : FileInputStream -> IoBridge.open -> Libcore.os.open ->  
 BlockGuardOs.open -> Posix.open

Libcore.java是一个不错的hook点

package libcore.io;
public final class Libcore {
    private Libcore() { }

    public static Os os = new BlockGuardOs(new Posix());
}


我们可以通过反射获取到这个Os变量,它是一个接口类型,里面定义了open、read、write、close方法,具体实现在BlockGuardOs里面。

// 反射获得静态变量
Class<?> clibcore = Class.forName("libcore.io.Libcore");
Field fos = clibcore.getDeclaredField("os");

通过动态代理的方式,在它所有IO方法前后加入插桩代码来统计IO信息

// 动态代理对象
Proxy.newProxyInstance(cPosix.getClassLoader(), getAllInterfaces(cPosix), this);

beforeInvoke(method, args, throwable);
result = method.invoke(mPosixOs, args);
afterInvoke(method, args, result);

此方案缺点如下:

  • 性能差,IO调用频繁,使用动态代理和Java的字符串操作,导致性能较差,无法达到线上使用标准
  • 无法监控Native代码,这个也是比较重要的
  • 兼容性差:需要根据Android 版本做适配,特别是Android P的非公开API限制
4.4.3 Native监控方案

Native Hook方案的核心从 libc.so 中的这几个函数中选定 Hook 的目标函数

int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t size);
ssize_t write(int fd, const void *buf, size_t size); write_cuk
int close(int fd);

我们需要选择一些有调用上面几个方法的 library,例如选择libjavacore.so、libopenjdkjvm.so、libopenjdkjvm.so,可以覆盖到所有的 Java 层的 I/O 调用。

不同版本的 Android 系统实现有所不同,在 Android 7.0 之后,我们还需要替换下面这三个方法。

open64
__read_chk
__write_chk

native hook 框架目前使用比较广泛的是爱奇艺的xhook ,以及它的改进版,字节跳动的bhook。

具体的native IO监控代码,可以参考 Matrix-IOCanary,内部使用的是xhook框架。

关于IO涉及到的知识非常多,后面有时间可以单独整理一篇文章。

接下来看看最后一种OOM类型~

五、内存不足

5.1 堆栈信息

内存不足.png

这种是最常见的OOM,Java堆内存不足,512M都不够玩~

发生此问题的大部分设备都是Android 7.0,高版本也有,不过相对较少。

5.2 重温JVM内存结构

JVM在运行时,将内存划分为以下5个部分

  1. 方法区:存放静态变量、常量、即时编译代码;
  2. 程序计数器:线程私有,记录当前执行的代码行数,方便在cpu切换到其它线程再回来的时候能够不迷路;
  3. Java虚拟机栈:线程私有,一个Java方法开始和结束,对应一个栈帧的入栈和出栈,栈帧里面有局部变量表、操作数栈、返回地址、符号引用等信息;
  4. 本地方法栈:线程私有,跟Java虚拟机栈的区别在于 这个是针对native方法;
  5. 堆:绝大部分对象创建都在堆分配内存

内存不足导致的OOM,一般都是由于Java堆内存不足,绝大部分对象都是在堆中分配内存,除此之外,大数组、以及Android3.0-7.0的Bitmap像素数据,都是存放在堆中。

基于这个结论,关于Java堆内存不足导致的OOM问题,优化方案主要是图片加载优化、内存泄漏监控。

5.3 图片加载优化

5.3.1 常规的图片优化方式
  1. 分析了主流图片库Glide和Fresco的优缺点,以及使用场景;
  2. 分析了设计一个图片加载框架需要考虑的问题;
  3. 防止图片占用内存过多导致OOM的三个方式:软引用、onLowMemory、Bitmap 像素存储位置

这篇文章现在来看还是有点意义的,其中的原理部分还没过时,不过技术更新迭代,常规的优化方式已经不太够了,长远考虑,可以做图片自动压缩、大图自动检测和告警。

5.3.2 无侵入性自动压缩图片

针对图片资源,设计师往往会追求高清效果,忽略图片大小,一般的做法是拿到图后手动压缩一下,这种手动的操作完全看个人修养。

无侵入性自动压缩图片,主流的方案是利用Gradle 的Task原理,在编译过程中,mergeResourcesTask 这个任务是将所以aar、module的资源进行合并,我们可以在mergeResourcesTask 之后可以拿到所有资源文件,具体做法:

  1. 在mergeResourcesTask这个任务后面,增加一个图片处理的Task,拿到所有资源文件;
  2. 拿到所有资源文件后,判断如果是图片文件,则通过压缩工具进行压缩,压缩后如果图片有变小,就将压缩过的图片替换掉原图。

可以简单理解如下: image.png

具体代码可以参考 McImage 这个库。

5.4 大图监控

5.3.2 自动压缩图片只是针对本地资源,而对于网络图片,如果加载的时候没有压缩,那么内存占用会比较大,这种情况就需要监控了。

5.4.1 从图片框架侧监控

很多App内部可能使用了多个图片库,例如Glide、Picasso、Fresco、ImageLoader、Coil,如果想监控某个图片框架, 那么我们需要熟读源码,找到hook点。

对于Glide,可以通过hook SingleRequest,它里面有个requestListeners,我们可以注册一个自己的监听,图片加载完做一个大图检测。

其它图片框架,同理也是先找到hook点,然后进行类似的hook操作就可以,代码可以参考:dokit-BigImgClassTransformer

5.4.2 从ImageView侧监控

5.4.1 是从图片加载框架侧监控大图,假如项目中使用到的图片加载框架太多,有些第三方SDK内部可能自己搞了图片加载,

这种情况下我们可以从ImageView控件侧做监控,监听setImageDrawable等方法,计算图片大小如果大于控件本身大小,debug包可以弹窗提示需要修改。

方案如下:

  1. 自定义ImageView,重写setImageDrawable、setImageBitmap、setImageResource、setBackground、setBackgroundResource这几个方法,在这些方法里面,检测Drawable大小;
  2. 编译期,修改字节码,将所有ImageView的创建都替换成自定义的ImageView;
  3. 为了不影响主线程,可以使用IdleHandler,在主线程空闲的时候再检测;

最终是希望当检测到大图的时候,debug环境能够弹窗提示开发进行修改,release环境可以上报后台。

debug如下效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qyrJo3Ih-1651225474031)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b9133dfddf514d6b8519a21c29044089~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

当然这种方案有个缺点:不能获取到图片url。

图片优化告一段落,接下来看看内存泄漏~

5.5 内存泄漏监控演进

LeakCanary

关于内存泄漏,大家可能都知道LeakCanary,只要添加一个依赖

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1',

就能实现自动检测和分析内存泄漏,并发出一个通知显示内存泄漏详情信息。

LeakCanary只能在debug环境使用,因为它是在当前进程dump内存快照,Debug.dumpHprofData(path);会冻结当前进程一段时间,整个 APP 会卡死约5~15s,低端机上可能要几十秒的时间。

ResourceCanary

微信对LeakCanary做了一些改造,将检测和分析分离,客户端只负责检测和dump内存镜像文件,文件裁剪后上报到服务端进行分析。

具体可以看这篇文章Matrix ResourceCanary – Activity 泄漏及Bitmap冗余检测

KOOM

不管是LeakCanary 还是 ResourceCanary,他们都只能在线下使用,而线上内存泄漏监控方案,目前KOOM的方案比较完善,下面我将基于KOOM分析线上内存泄漏监控方案的核心流程。

5.6 线上内存泄漏监控方案

基于KOOM源码分析

5.6.1 检测时机
  1. 间隔5s检测一次
  2. 触发内存镜像采集的条件:
  • 当内存使用率达到80%以上
      //->OOMMonitorConfig
      
      private val DEFAULT_HEAP_THRESHOLD by lazy {
        val maxMem = SizeUnit.BYTE.toMB(Runtime.getRuntime().maxMemory())
        when {
          maxMem >= 512 - 10 -> 0.8f
          maxMem >= 256 - 10 -> 0.85f
          else -> 0.9f
        }
      }

  • 两次检测时间内(例如5s内),内存使用率增加5%
5.6.2 内存镜像采集

我们知道LeakCanary检测内存泄漏,不能用于线上,是因为它dump内存镜像是在当前进程进行操作,会冻结App一段时间。

所以,作为线上OOM监控,dump内存镜像需要单独开一个进程。

整体的策略是:

虚拟机supend->fork虚拟机进程->虚拟机resume->dump内存镜像的策略。

dump内存镜像的源码如下:

  //->ForkJvmHeapDumper
  
  public boolean dump(String path) {
    ...

    boolean dumpRes = false;
    try {
      //1、通过fork函数创建子进程,会返回两次,通过pid判断是父进程还是子进程
      int pid = suspendAndFork();
      
      MonitorLog.i(TAG, "suspendAndFork,pid="+pid);
      if (pid == 0) {
        //2、子进程返回,dump内存操作,dump内存完成,退出子进程
        Debug.dumpHprofData(path);
        exitProcess();
      } else if (pid > 0) {
        // 3、父进程返回,恢复虚拟机,将子进程的pid传过去,阻塞等待子进程结束
        dumpRes = resumeAndWait(pid);
        MonitorLog.i(TAG, "notify from pid " + pid);
      }
    }
    return dumpRes;
  }

注释1:父进程调用native方法挂起虚拟机,并且创建子进程;
注释2:子进程创建成功,执行Debug.dumpHprofData,执行完后退出子进程;
注释3:得知子进程创建成功后,父进程恢复虚拟机,解除冻结,并且当前线程等待子进程结束。

注释1源码如下:

// ->native_bridge.cpp

pid_t HprofDump::SuspendAndFork() {
  //1、暂停VM,不同Android版本兼容
  if (android_api_ < __ANDROID_API_R__) {
    suspend_vm_fnc_();
  }
  ...

  //2,fork子进程,通过返回值可以判断是主进程还是子进程
  pid_t pid = fork();
  if (pid == 0) {
    // Set timeout for child process
    alarm(60);
    prctl(PR_SET_NAME, "forked-dump-process");
  }
  return pid;
}


注释3源码如下:

//->hprof_dump.cpp

bool HprofDump::ResumeAndWait(pid_t pid) {
  //1、恢复虚拟机,兼容不同Android版本
  if (android_api_ < __ANDROID_API_R__) {
    resume_vm_fnc_();
  }
  ...
  int status;
  for (;;) {
    //2、waitpid,等待子进程结束
    if (waitpid(pid, &status, 0) != -1 || errno != EINTR) {
      //进程异常退出
      if (!WIFEXITED(status)) {
        ALOGE("Child process %d exited with status %d, terminated by signal %d",
              pid, WEXITSTATUS(status), WTERMSIG(status));
        return false;
      }
      return true;
    }
    return false;
  }
}

这里主要是利用Linux的waitpid函数,主进程可以等待子进程dump结束,然后再返回执行内存镜像文件分析操作。

5.6.3 内存镜像分析

前面一步已经通过Debug.dumpHprofData(path)拿到内存镜像文件,接下来就开启一个后台服务来处理

 //->HeapAnalysisService
 
  override fun onHandleIntent(intent: Intent?) {
    ...
    kotlin.runCatching {
      //1、通过shark将hprof文件转换成HeapGraph对象
      buildIndex(hprofFile)
    }
    ...
    //2、将设备信息封装成json
    buildJson(intent)

    kotlin.runCatching {
      //3、过滤泄漏对象,有几个规制
      filterLeakingObjects()
    }
    ...
    kotlin.runCatching {
      // 4、gcRoot是否可达,判断内存泄漏
      findPathsToGcRoot()
    }
    ...

    //5、泄漏信息填充到json中,然后结束了
    fillJsonFile(jsonFile)


    //通知主进程内存泄漏分析成功
    resultReceiver?.send(AnalysisReceiver.RESULT_CODE_OK, null)

    //这个服务是在单独进程,分析完就退出
    System.exit(0);
  }

内存镜像分析的流程如下:

  1. 通过shark这个开源库将hprof文件转换成HeapGraph对象
  2. 收集设备信息,封装成json,现场信息很重要
  3. filterLeakingObjects:过滤出泄漏的对象,有一些规制,例如已经destroyed和finished的activity、fragment manager为空的fragment、已经destroyed的window等。
  4. findPathsToGcRoot:内存泄漏的对象,查找其到GcRoot的路径,通过这一步就可以揪出内存泄漏的原因
  5. fillJsonFile:格式化输出内存泄漏信息
小结

线上Java内存泄漏监控方案分析,这里小结一下:

  1. 挂起当前进程,然后通过fork创建子进程;
  2. fork会返回两次,一次是子进程,一次是父进程,通过返回的pid可以判断是子进程还是父进程;
  3. 如果是父进程返回,则通过resumeAndWait恢复进程,然后当前线程阻塞等待子进程结束;
  4. 如果子进程返回,通过Debug.dumpHprofData(path)读取内存镜像信息,这个会比较耗时,执行结束就退出子进程;
  5. 子进程退出,父进程的resumeAndWait就会返回,这时候就可以开启一个服务,后台分析内存泄漏情况,这块跟LeakCanary的分析内存泄漏原理基本差不多。

不画图了,结合源码看应该可以理解。

5.7 native内存泄漏监控

对于Java内存泄漏监控,线下我们可以使用LeakCanary、线上可以使用KOOM,而对于native内存泄漏应该如何监控呢?

方案如下:

首先要了解native层
申请内存的函数:malloc、realloc、calloc、memalign、posix_memalign
释放内存的函数:free

  1. hook申请内存和释放内存的函数

hook申请内存和释放内存的函数.png

分配内存的时候,收集堆栈、内存大小、地址、线程等信息,存放到map中,在释放内存的时候从map中移除。

从map中移除.png

那怎么判断native内存泄漏呢?

  • 周期性的使用 mark-and-sweep 分析整个进程 Native Heap,获取不可达的内存块信息「地址、大小」
  • 获取到不可达的内存块的地址后,可以从我们的Map中获取其堆栈、内存大小、地址、线程等信息。

具体实现可以参考:koom-native-leak

总结

本文从线上OOM问题入手,介绍了OOM原理, 以及OOM优化方案和监控方案,基本上都是大厂开源出来的比较成熟的方案:

  1. 对于pthread_create OOM问题,介绍了无侵入性的new Thread优化、无侵入性的线程池优化、以及线程泄漏监控;
  2. 对于文件描述符过多问题,介绍了原理以及文件描述符监控方案、IO监控方案;
  3. 对于Java内存不足导致的OOM、介绍了无侵入性图片自动压缩方案、两种无侵入性的大图监控方案、Java内存泄漏监控的线下方案和线上方案、以及native内存泄漏监控方案。

大厂对外开源的技术非常多,但不一定最优,我们在学习过程中可以多加思考, 例如线程优化,booster 对于new Thread的优化只是设置了线程名,有助于分析问题,而经过我的猜想和验证,通过字节码插桩,将new Thread无侵入性替换成线程池调用,才是真正意义上的线程优化。


ios 内存监控 最近看到腾讯的一个内存监控组件:OOMDetector,于是加到当前的工程里面测试一下。 OOMDetector地址:点击打开链接 按着给的demo,把组件加入到当前工程,真机跑起来,还真发现了1个内存泄漏,但是给出的提示地址是真实的内存地址,需要转换。看了组件使用介绍,看了半天,才注意到文章写的堆栈翻译,又在工程里面找,终于找到了translate_oom.py这个工具。 以下就是具体 阅读详情

相关推荐

Swift-OOMDetector是腾讯开源的一个iOS的内存监控组件

iOS的内存监控组件,为您提供OOM监控,内存分配监控,内存泄漏检测等功能。

iOS总结-检测内存泄漏库-MLeaksFinder源码解析

MLeaksFinder是WeRead团队开源的一款检测iOS内存泄漏的AOP框架。 优点:无侵入性  可以构建泄漏堆栈  有白名单机制  扩展性 其他一些特殊处理 主要涉及到的是基类NSObject+MemoryLeak.h类别里面,- (BOOL)willDealloc方法 -assertNotDealloc方法是打印内存泄漏方法名 有关MLeakedObjectProxy类的...

qq_28551705的博客 651

逆向选择启示录:一个失败高频策略如何意外跑赢市场

本文探讨了高频交易中的逆向选择现象,揭示了一个失败的高频策略如何通过简化意外跑赢市场。通过加密货币市场的实证分析和蒙特卡洛模拟,研究发现低预测性的简单策略反而更具鲁棒性,有效规避了逆向选择风险。文章还提供了小币种做市策略的优化框架和实践建议,为量化交易者提供了宝贵洞见。

weixin_29191669的博客 401

OOMDetector 使用教程

OOMDetector 是一个由腾讯开发的 iOS 内存监控组件,旨在帮助开发者轻松实现 OOM(Out of Memory)监控、大内存分配监控、内存泄漏检测等功能。该组件通过提供详细的内存分配堆栈信息,帮助开发者快速定位和解决内存相关问题。 ### 主要特性 - **OOM 监控**:监控应用的 OOM 情况,并记录导致 OOM 的堆栈信息。 - **大内存分配监控**:监控单次大块内存分配...

gitblog_00818的博客 913

经典烧脑算法逻辑题--高楼扔鸡蛋:最少次数测出鸡蛋摔碎临界点。

如果在第2层没碎,换到第3层扔.......如果第59层没碎,换到第60层扔;有2个鸡蛋,从100层楼上往下扔,以此来测试鸡蛋的硬度。比如鸡蛋在第9层没有摔碎,在第10层摔碎了,那么鸡蛋不会摔碎的临界点就是9层。因此,我们尝试每10层扔一次,第一次从10层扔,第二次从20层扔,第三次从30层......一直扔到100层。如果第一枚鸡蛋,在50层碎了,第二枚鸡蛋,就从第1层开始扔,一层一层增长,一直扔到第49层。如果第一枚鸡蛋在50层没碎了,则继续使用二分法,在剩余楼层的一半(75层)往下扔。

借雨醉东风的博客 1817

Veristand自动化测试:Stimulus Profile Editor实战指南

本文是Veristand自动化测试中Stimulus Profile Editor的实战指南。详细介绍了如何利用这一图形化工具,将繁琐的手动测试流程转化为一键自动执行的“剧本”,涵盖工程控制、信号编程、预制动作调用等核心功能,并通过手把手案例演示了构建首个自动化测试脚本的全过程,帮助测试工程师显著提升效率与可靠性。

dd012的博客 492

【亲测免费】 OOMDetector:iOS内存监控的利器

**许可证**:MIT

gitblog_00571的博客 1367

Ubuntu安装opencv4.5(自己安装过程详细)

1.下载opencv 在opencv官网下载source版,下载链接 http://opencv.org/releases.html。别下载错版本 下载过程就不多说了。 2.安装过程 2.1解压 unzip opencv-4.5.0.zip cd opencv-4.5.0 2.2安装依赖的库 sudo apt-get install cmake sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-

weixin_45629790的博客 1万+

JVM 性能监控与调优篇 概述篇

概述篇 一、大厂面试题 提前了解一下要学什么 支付宝: 支付宝三面:JVM性能调优都做了什么? 小米: 有做过JVM内存优化吗? 从SQL、JVM、架构、数据库四个方面讲讲优化思路 蚂蚁金服: JVM的编译优化 jvm性能调优都做了什么 JVM诊断调优工具用过哪些? 二面:jvm怎样调优,堆内存、栈空间设置多少合适 三面:JVM相关的分析工具使用过的有哪些?具体的性能调优步骤如何 阿里: 如何进行JVM调优?有哪些方法? 如何理解内存泄漏问题?有哪些情况会导致内存泄漏?如何解决? 字节跳动:

weixin_43967582的博客 465

企业网络改造必看:如何用华为S5700三层交换机解决多部门隔离通信?

本文详细介绍了如何利用华为S5700三层交换机解决企业多部门网络隔离与通信需求。通过划分VLAN实现逻辑隔离,并配置三层路由实现受控互通,结合ACL进行精细访问控制,构建安全高效的企业网络。文章提供了从拓扑规划、地址分配到核心配置的完整实战指南。

weixin_29260291的博客 260

合并数组与非合并数组——SystemVerilog

合并型数组(packed): — 合并型数组可以实现连续的存储,赋值时不需要用 '{ }。 //合并型数组 bit [3] [7:0] b_pack // 表示3行 x 8列的数组,[3]是高维度(高维度),[7:0]是低维度(第一维) 数组中,数据排列为{b_pack[3],b_pack[2],b_pack[1],b_pack[0]},其中每个b_pack为8个bit; bit是二值逻辑,每位bit只占据1位。故24位bit只占据一个word(一般一个word为32bit)的存储空间

SummerXRT的博客 7638

大厂的OOM优化和监控方案

速领:神作《凤凰架构:构建可靠的大型分布式系统》电子版随着项目不断壮大,OOM(Out Of Memory)成为崩溃统计平台上的疑难杂症之一,大部分业务开发人员对于线上OOM问题一般都是暂不处理,一方面是因为OOM问题没有足够的log,无法在短期内分析解决,另一方面可能是忙于业务迭代、身心疲惫,没有精力去研究OOM的解决方案。这篇文章将以线上OOM问题作为切入点,介绍常见...

程序猿DD 689

AES攻击方法 :差分密码分析 boomerang attack飞去来器攻击

AES具有123比特的分组长度,三种可选的密钥长度(128,192,256) AES是一个迭代型密码,轮数依赖于密钥长度。如果密钥长度为128比特,则轮数为10;如果密钥长度为192比特,则轮数为12;如果密钥长度为256比特,则轮数为14; 混淆(Confusion) 混淆是为了让密钥与密文的统计信息之间的关系变得复杂,从而增加通过统计方法进行攻击的难度。混淆可以通过各种代换算法实现,如S盒、乘法(加)与异或等运算组合,为非线性部件。 扩散(Diffusion) 扩散是为了让明文的单个比特影像到

m0_67105022的博客 3491

《Android性能优化大厂实战解析》揭秘一线大厂性能优化底层原理与实战技巧

现在出现了许多便捷易用的技术,上手的容易程度让人产生自我技术能力超强的错觉,但实际上,我们往往忽视了对底层原理的深入探究。在Android开发领域,很多开发者仅停留在对多种性能优化技术“有所耳闻”或“简单应用”的层面,而未能触及技术核心。布局复杂度:复杂的布局结构会延长渲染时间,导致应用启动缓慢、页面切换卡顿;能耗问题:高耗电量会导致设备发热加剧,缩短电池续航时间;内存管理:内存消耗过大不仅会造成频繁的垃圾回收(GC),GC 过程中会暂停其他任务执行,从而引发界面卡顿;

yihanss的博客 1216

示波器核心参数选型指南:带宽、采样率与嵌入式调试实战

示波器是电子工程师进行信号观测与故障诊断的基础仪器,其性能由带宽、采样率、通道数、存储深度、触发系统和探头特性共同决定。带宽定义高频响应边界,需遵循5倍法则以保障数字信号上升沿测量精度;采样率则依据奈奎斯特定理并结合工程冗余(通常≥2.5×带宽)实现可靠时域重建。二者协同影响信号完整性评估能力,尤其在UART、I²C、SPI等嵌入式总线调试中至关重要。高存储深度支持长时捕获与毛刺定位,智能触发与硬件协议解码进一步将波形分析升维至语义级。本文围绕真实嵌入式场景,解析参数间的耦合关系与典型误用陷阱。

weixin_42588877的博客 91

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

一线大厂 Android 性能优化总结

​现在安卓系统无论是性能还是体验上其实都不输于iOS,只是因为手机厂商多而杂,他们会改源码,自定义系统,最后又过一遍不同开发水平工程师的手,导致很多手机即使在机器上面的跑分非常高,里面的APP运行也有卡顿现象。 而且这种卡顿会随着产品的更新迭代,功能的越发复杂,UI页面的越发丰富,变得更加严重。 但是,产品功能的更新需求,新功能的开发和UI的丰富都是用户的需求,是不可逆的趋势。在这样的情况下,优秀的性能优化人才一直是几大头部互联网公司高价竞聘的对象。 性能优化的目的 流畅(解决:卡顿) 稳定(解决:内存溢

qq_44102588的博客 651

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

OOM!又是OOM!Android 内存监控一定要注意!

前言 性能优化是一个老生常谈的问题,也一直是Android中高级攻城狮进阶和深入的方向。 在性能优化的各种问题中,内存泄漏一直占有较重要的地位。 内存泄漏的积累会造成内存溢出,进而导致程序崩溃,出现OOM问题。 针对这个问题,我们需要在上线前进行检测同时也要能够实现线上的监控。 方案一:线下性能监控 Android Studio自身提供的MAT等工具分析内存泄漏比较耗时,因而诞生了LeakCanary——这也是现在寻找内存泄漏问题最常用的工具。 LeakCanary 这个工具非常简单,依赖 LeakCana

BUGgogogo的博客 525

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

看看各大厂是如何进行性能优化的?

“面试造火箭,工作拧螺丝“。无数开发者在面试官面前都深感无力,在如今的面试中,性能优化问题成为了众多面试者的噩梦:面试官:性能优化你了解么?我:有。面试官:你都做过哪方面的性能优化呢...

weixin_38754349的博客 139

大意了,又是 OOM ,Android 内存监控一定要注意这几点

针对这个问题,我们需要在上线前进行检测同时也要能够实现线上的监控。方案一:线下性能监控==========Android Studio自身提供的MAT等工具分析内存泄漏比较耗时,因而诞生了LeakCanary——这也是现在寻找内存泄漏问题最常用的工具。LeakCanary 这个工具非常简单,依赖 LeakCanary 库,可自动检测内存泄漏并通过通知报警,不用初始化。但是经过对它的深入分析之后,不难发现,简单的API后面藏着许多复杂的逻辑处理原理。

2401_83620959的博客 1132

掌握性能优化,突破Android开发瓶颈,赢取大厂青睐

Android开发者要想在竞争激烈的职场中脱颖而出,就必须重视性能优化,深入理解底层原理,掌握各种优化技巧。只有这样,才能在大厂面试中脱颖而出,成为一名真正的高级Android开发者。在未来,具备性能优化能力的开发者将更加受到企业的青睐,拥有更广阔的职业发展空间。

Android23333的博客 658

OOM 层出不穷,这份 《Android性能优化大厂实战全解析》笔记带你直击命门

在性能优化中,大家可能会比较关注UI界面优化、耗损(电量,流量)优化,但其中内存优化才是命门所在。内存作为程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏,重则导致用户应用程序发生 OOM(out of memory)崩溃。但是很多时候由于项目需要,大家都是拼命的加班加点的赶进度,为了追求效率很多代码可能就写的不(放)够(...

weixin_38754349的博客 260

能跑就行?掌握了它,就搞定了APP性能优化一半的问题!

Android高工绕不开的性能优化不管是对于Android开发者,还是对于整个程序员行业而言,性能优化已然成为了用来区别初中级工程师和高级工程师的“分水岭”。随着移动互联网的高速发展,以及...

区块链大本营 3601

包含了腾讯、百度、字节等大厂的Android性能优化实战教程

等大厂的性能优化实战,既能够夯实底层原理、性能调优等核心技术点,又能够掌握普通开发者,难以触及的架构设计方法论。让你在工作中、团队里、面试时,拥有同行难以复制的核心竞争力。那么在针对性能优化这个点,特意给大家准备了这份《

Eqiqi的博客 755

大意了,又是 OOM ,Android 内存监控一定要注意这几点(1)

现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!Android架构视频+BAT面试专题PDF+学习笔记,或者私信回复【技能提升】即可获取!

2401_83620927的博客 861

7年Android开发总结的《Android性能优化大厂实战》,谁能不心动啊?!!

近两年的Android开发领域,受互联网行业变化的影响,整体环境呈现出下行趋势。在这种大环境下,各大公司纷纷收缩业务线,裁员成为常态,公司内部的竞争愈发激烈。作为Android开发者,要想在这种局面中脱颖而出,性能优化成为了一项关键的技能。作为一名有着7年Android开发经验的博主,我也曾长期从事性能相关的工作,接触过众多模块。然而,要做好性能优化并不容易。这是因为,学习性能优化需要了解的知识点非常多,而且各个知识点在Android系统中是相互关联的,牵一发而动全身。

Android23333的博客 666
上一篇: EventBus源码赏析一 —— 基本使用
下一篇: 字节二面:如何应对Framework层中多进程和线程通信场景
Agent学习路线
博客等级 码龄6年 1万+粉丝 1902原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值