Android性能优化之Parcel OOM问题分析

近期在对游戏进行内存优化时,bugly上出现一个较为其他的OOM问题:跨进程通讯Parcel 通讯发生OOM。

java堆栈:
在这里插入图片描述
源码分析过程

锁定Parcel 的nativeWriteString16()开始查看。
Parcel: http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/core/jni/android_os_Parcel.cpp#android_os_Parcel_writeString16
这个nativeWriteString16()函数对应的jni 层中的android_os_Parcel_writeString16()函数:
在这里插入图片描述
接下来看下android_os_Parcel_writeString16():

297  static void android_os_Parcel_writeString16(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)298  {
    
    
299      Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); //先获取到c++层Parcel指针对象
300      if (parcel != NULL) {
    
    
301          status_t err = NO_MEMORY;
302          if (val) {
    
    
                 //接着,获取Java层String的指针
303              const jchar* str = env->GetStringCritical(val, 0);
304              if (str) {
    
    //获取成功
                     //接着通过Parce指针对象的writeString16()继续写入
305                  err = parcel->writeString16(
306                      reinterpret_cast<const char16_t*>(str),
307                      env->GetStringLength(val));
308                  env->ReleaseStringCritical(val, str);
309              }
                 //注意点:若代码执行到这里,则表明存在内存异常,获取失败,这时erro 是 NO_MEMORY
310          } else {
    
    
                 //因java层string 是空对象,这里写入空值
311              err = parcel->writeString16(NULL, 0);
312          }
313          if (err != NO_ERROR) {
    
    //存在err异常状态,则抛出对应的异常。
314              signalExceptionForError(env, clazz, err);
315          }
316      }
317  }

从以上代码可知: 首先获取c++层Parcel指针对象,接着获取Java层String的指针,最后通过Parce指针对象的writeString16()继续写入。

先看下signalExceptionForError()
在这里插入图片描述
结合以上源码可知,执行nativeWriteString16()后,error是为NO_MEMORY,从而程序导致抛出OOM异常。

导致error是为NO_MEMORY 为的原因可能有:

  • 执行env->GetStringCritical(val, 0) ,存在内存溢出,返回Null 所导致;
  • 执行parcel->writeString16()返回所导致;

先简单来了解下c++ Parcel类:其内部也是有buffer数据缓存的设计,有capacity容量,pos位置等等

在这里插入图片描述
再通过一张图来了解下这几个概念:
在这里插入图片描述
但在初始化构造函数时,默认都是为0的,等真正使用时候,才会计数赋值:
在这里插入图片描述
接下来, 继续查看Parcel的writeString16()

http://aospxref.com/android-11.0.0_r21/xref/frameworks/native/libs/binder/Parcel.cpp#1036

1036 status_t Parcel::writeString16(const char16_t* str, size_t len){
    
    
1038      if (str == nullptr) return writeInt32(-1);//若java层string是空,则写入-1标识
             //更新 mDataPos位置 ,存在扩容growData()可能返回NO_MEMORY
1039         status_t err = writeInt32(len);
1041      if (err == NO_ERROR) {
    
    
1042          len *= sizeof(char16_t);
              //writeInplace计算复制数据的目标所在的地址
1043          uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char16_t));
1044          if (data) {
    
    
                 //根据地址拷贝数据过去,实现跨进程通讯
1045              memcpy(data, str, len);
1046              *reinterpret_cast<char16_t*>(data+len) = 0;
1047              return NO_ERROR;
1048          }
1049          err = mError;
1050      }
1051      return err;
1052  }

每个进程的内存是私有的,不共享。像传递int类型(1)这样的数据,多进程直接进行拷贝就可以,但引用对象,在内存中是地址0xxxx,在其他进程中是不存在,无法直接拷贝过去。因此需要进程A需要将数据进行数据打包(计算出地址+将数据拷贝到该地址),而进程B进行数据解析还原(拿到地址+将地址中数据读取到来)从而实现对引用类型数据的传递。

writeInplace() 是计算出地址的过程:
在这里插入图片描述
接下来看下growData():若是条件允许,则会扩容需要大小的1.5倍。

在这里插入图片描述
从上面可知,当需要扩容的长度超出指定范围,也会返回NO_MEMORY,从而导致OOM.

Parcel发生OOM的情况有两种:

  • 执行env->GetStringCritical(val, 0) ,存在内存溢出,返回Null 所导致;
  • 执行growData()进行扩容导致,超出限制长度,所导致

推断分析:

通过源码知道发生原因,在去bugly上查找有用的日志,发现有一个很关键的日志:
在这里插入图片描述
结论:进程虚拟内存即将达到4G峰值,与此同时,正在进行跨进程parcel 通讯,执行到env->GetStringCritical() 获取java层String指针,内存溢出,返回为Null ,从而抛出OOM。

猜你喜欢

转载自blog.csdn.net/hexingen/article/details/131958815
今日推荐