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:
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:
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()
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.
Use a picture to understand these concepts:
But when initializing the constructor, the default is 0, and when it is actually used, it will be counted and assigned:
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:
let’s look at it next growData()
: if conditions permit, the capacity will be expanded by 1.5 times the required size.
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
: 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.