开发中如果内存管理没有做好,那么就会可能出现OOM报错导致应用崩溃。
我们通过搜索代码中的OOM字样,发现了几个抛出OOM的地方
创建native线程失败
原因参考https://blog.csdn.net/aiynmimi/article/details/126991015
art\runtime\thread.cc
bash
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
....................
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
{
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());
}
}
堆分配内存失败
art\runtime\heap.cc
bash
mirror::Object* Heap::AllocateInternalWithGc(Thread* self,
AllocatorType allocator,
bool instrumented,
size_t alloc_size,
size_t* bytes_allocated,
size_t* usable_size,
size_t* bytes_tl_bulk_allocated,
ObjPtr<mirror::Class>* klass) {
..................................
// If the allocation hasn't succeeded by this point, throw an OOM error.
if (ptr == nullptr) {
ScopedAllowThreadSuspension ats;
ThrowOutOfMemoryError(self, alloc_size, allocator);
}
return ptr;
}
art\runtime\jni\jni_internal.cc
该方法出现在EnsureLocalCapacity和PushLocalFrame函数调用中(管理局部引用)
bash
static jint EnsureLocalCapacityInternal(ScopedObjectAccess& soa, jint desired_capacity,
const char* caller)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (desired_capacity < 0) {
LOG(ERROR) << "Invalid capacity given to " << caller << ": " << desired_capacity;
return JNI_ERR;
}
std::string error_msg;
if (!soa.Env()->locals_.EnsureFreeCapacity(static_cast<size_t>(desired_capacity), &error_msg)) {
std::string caller_error = android::base::StringPrintf("%s: %s", caller, error_msg.c_str());
soa.Self()->ThrowOutOfMemoryError(caller_error.c_str());
return JNI_ERR;
}
return JNI_OK;
}
定位到该函数中,从字面意思,理解为申请desired_capacity个内存空间,但是未能申请到足够的空间
bash
bool IndirectReferenceTable::EnsureFreeCapacity(size_t free_capacity, std::string* error_msg) {
size_t top_index = segment_state_.top_index;
if (top_index < max_entries_ && top_index + free_capacity <= max_entries_) {
return true;
}
// We're only gonna do a simple best-effort here, ensuring the asked-for capacity at the end.
if (resizable_ == ResizableCapacity::kNo) {
*error_msg = "Table is not resizable";
return false;
}
// Try to increase the table size.
// Would this overflow?
if (std::numeric_limits<size_t>::max() - free_capacity < top_index) {
*error_msg = "Cannot resize table, overflow.";
return false;
}
if (!Resize(top_index + free_capacity, error_msg)) {
LOG(WARNING) << "JNI ERROR: Unable to reserve space in EnsureFreeCapacity (" << free_capacity
<< "): " << std::endl
<< MutatorLockedDumpable<IndirectReferenceTable>(*this)
<< " Resizing failed: " << *error_msg;
return false;
}
return true;
}
转Jstring的char*数据过多
bash
static jstring NewStringUTF(JNIEnv* env, const char* utf) {
.....................
size_t utf8_length = strlen(utf);
if (UNLIKELY(utf8_length > static_cast<uint32_t>(std::numeric_limits<int32_t>::max()))) {
// Converting the utf8_length to int32_t for String::AllocFromModifiedUtf8() would
// overflow. Throw OOME eagerly to avoid 2GiB allocation when trying to replace
// invalid sequences (even if such replacements could reduce the size below 2GiB).
std::string error =
android::base::StringPrintf("NewStringUTF input is 2 GiB or more: %zu", utf8_length);
ScopedObjectAccess soa(env);
soa.Self()->ThrowOutOfMemoryError(error.c_str());
return nullptr;
}
}
String
art\runtime\jni\string-alloc-inl.cc
bash
const size_t max_length = RoundDown(max_alloc_length, kObjectAlignment / block_size);
if (UNLIKELY(length > max_length)) {
self->ThrowOutOfMemoryError(
android::base::StringPrintf("%s of length %d would overflow",
Class::PrettyDescriptor(string_class).c_str(),
static_cast<int>(length)).c_str());
return nullptr;
}
Array
art\runtime\jni\array-alloc-inl.cc
bash
size_t size = ComputeArraySize(component_count, component_size_shift);
#ifdef __LP64__
// 64-bit. No size_t overflow.
DCHECK_NE(size, 0U);
#else
// 32-bit.
if (UNLIKELY(size == 0)) {
self->ThrowOutOfMemoryError(android::base::StringPrintf("%s of length %d would overflow",
array_class->PrettyDescriptor().c_str(),
component_count).c_str());
return nullptr;
}
为什么64bit不会报错呢,我们看到32位有长度限制
bash
static inline size_t ComputeArraySize(int32_t component_count, size_t component_size_shift) {
DCHECK_GE(component_count, 0);
size_t component_size = 1U << component_size_shift;
size_t header_size = Array::DataOffset(component_size).SizeValue();
size_t data_size = static_cast<size_t>(component_count) << component_size_shift;
size_t size = header_size + data_size;
// Check for size_t overflow if this was an unreasonable request
// but let the caller throw OutOfMemoryError.
#ifdef __LP64__
// 64-bit. No overflow as component_count is 32-bit and the maximum
// component size is 8.
DCHECK_LE((1U << component_size_shift), 8U);
#else
// 32-bit.
DCHECK_NE(header_size, 0U);
DCHECK_EQ(RoundUp(header_size, component_size), header_size);
// The array length limit (exclusive).
const size_t length_limit = (0U - header_size) >> component_size_shift;
if (UNLIKELY(length_limit <= static_cast<size_t>(component_count))) {
return 0; // failure
}
#endif
return size;
}
如果规避该问题
1、内存优化
2、使用多进程编程
3、内存泄露排查