Parcel OOM problem analysis of Android performance optimization

Recently, when optimizing the memory of the game, a relatively other OOM problem appeared on bugly: OOM occurred in cross-process communication Parcel communication.

Java stack:
insert image description here
source code analysis process :

Lock Parcel's nativeWriteString16() to start viewing.
Parcel: http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/core/jni/android_os_Parcel.cpp#android_os_Parcel_writeString16
The android_os_Parcel_writeString16() function in the jni layer corresponding to this nativeWriteString16() function:
insert image description here
Next, look at 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  }

writeString16()From the above code, it can be seen that: first obtain the Parcel pointer object of the c++ layer, then obtain the pointer of the String of the Java layer, and finally continue writing through the Parce pointer object .

First look at signalExceptionForError()
insert image description here
combined with the above source code. After executing nativeWriteString16(), the error is NO_MEMORY, so the program causes an OOM exception to be thrown.

The reasons why the error is NO_MEMORY may be:

  • Execution env->GetStringCritical(val, 0) , there is a memory overflow, which is caused by returning Null;
  • Execution parcel->writeString16()returns caused by;

First, let’s briefly understand the c++ Parcel class: it also has a buffer data cache design, capacity capacity, pos position, etc.

insert image description here
Use a picture to understand these concepts:
insert image description here
But when initializing the constructor, the default is 0, and when it is actually used, it will be counted and assigned:
insert image description here
Next, continue to look at 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  }

The memory of each process is private and not shared. For data such as passing int type (1), multiple processes can directly copy it, but the reference object is at address 0xxxx in memory, which does not exist in other processes and cannot be copied directly. Therefore, process A needs to pack the data (calculate the address + copy the data to the address), and process B performs data analysis and restoration (get the address + read the data in the address) to realize the reference type data. transfer.

writeInplace() It is the process of calculating the address:
insert image description here
let’s look at it next growData(): if conditions permit, the capacity will be expanded by 1.5 times the required size.

insert image description here
It can be seen from the above that when the length that needs to be expanded exceeds the specified range, it will also return NO_MEMORY, resulting in OOM.

There are two situations in which OOM occurs in Parcel:

  • Execution env->GetStringCritical(val, 0), there is a memory overflow, which is caused by returning Null;
  • Execution growData()of expansion results in exceeding the limit length, resulting in

Inference analysis:

Know the cause of the occurrence through the source code, go to bugly to find useful logs, and find that there is a very critical log: Conclusion
insert image description here
: The process virtual memory is about to reach the peak of 4G. At the same time, cross-process parcel communication is in progress, and it is executed to env->GetStringCritical () Obtaining the Java layer String pointer, the memory overflows, and the return is Null, thus throwing OOM.

Guess you like

Origin blog.csdn.net/hexingen/article/details/131958815