淺談Java中OutOfMemoryError問(wèn)題產(chǎn)生原因
背景
其實(shí)這個(gè)問(wèn)題也挺有趣的,OutOfMemoryError,算是我們常見(jiàn)的一個(gè)錯(cuò)誤了,大大小小的APP,永遠(yuǎn)也逃離不了這個(gè)Error,那么,OutOfMemroyError是不是只有才分配內(nèi)存的時(shí)候才會(huì)發(fā)生呢?是不是只有新建對(duì)象的時(shí)候才會(huì)發(fā)生呢?要弄清楚這個(gè)問(wèn)題,我們就要了解一下這個(gè)Error產(chǎn)生的過(guò)程。
OutOfMemoryError
我們常常在堆棧中看到的OOM日志,大多數(shù)是在java層,其實(shí),真正被設(shè)置OOM的,是在ThrowOutOfMemoryError這個(gè)native方法中
void Thread::ThrowOutOfMemoryError(const char* msg) { LOG(WARNING) << "Throwing OutOfMemoryError " << '"' << msg << '"' << " (VmSize " << GetProcessStatus("VmSize") << (tls32_.throwing_OutOfMemoryError ? ", recursive case)" : ")"); ScopedTrace trace("OutOfMemoryError"); jni調(diào)用設(shè)置ERROR if (!tls32_.throwing_OutOfMemoryError) { tls32_.throwing_OutOfMemoryError = true; ThrowNewException("Ljava/lang/OutOfMemoryError;", msg); tls32_.throwing_OutOfMemoryError = false; } else { Dump(LOG_STREAM(WARNING)); // The pre-allocated OOME has no stack, so help out and log one. SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME()); } }
下面,我們就來(lái)看看,常見(jiàn)的拋出OOM的幾個(gè)路徑
MakeSingleDexFile
在ART中,是支持合成單個(gè)Dex的,它在ClassPreDefine階段,會(huì)嘗試把符合條件的Class(比如非數(shù)據(jù)/私有類)進(jìn)行單Dex生成,這里我們不深入細(xì)節(jié)流程,我們看下,如果此時(shí)把舊數(shù)據(jù)orig_location移動(dòng)到新的final_data數(shù)組里面失敗,就會(huì)觸發(fā)OOM
static std::unique_ptr<const art::DexFile> MakeSingleDexFile(art::Thread* self, const char* descriptor, const std::string& orig_location, jint final_len, const unsigned char* final_dex_data) REQUIRES_SHARED(art::Locks::mutator_lock_) { // Make the mmap std::string error_msg; art::ArrayRef<const unsigned char> final_data(final_dex_data, final_len); art::MemMap map = Redefiner::MoveDataToMemMap(orig_location, final_data, &error_msg); if (!map.IsValid()) { LOG(WARNING) << "Unable to allocate mmap for redefined dex file! Error was: " << error_msg; self->ThrowOutOfMemoryError(StringPrintf( "Unable to allocate dex file for transformation of %s", descriptor).c_str()); return nullptr; }
unsafe創(chuàng)建
我們java層也有一個(gè)很神奇的類,它也能夠操作指針,同時(shí)也能直接創(chuàng)建類對(duì)象,并操控對(duì)象的內(nèi)存指針數(shù)據(jù)嗎,它就是Unsafe,gson里面就大量用到了unsafe去嘗試創(chuàng)建對(duì)象的例子,比如需要?jiǎng)?chuàng)建的對(duì)象沒(méi)有空參數(shù)構(gòu)造函數(shù),這里如果malloc分配內(nèi)存失敗,也會(huì)產(chǎn)生OOM
static jlong Unsafe_allocateMemory(JNIEnv* env, jobject, jlong bytes) { ScopedFastNativeObjectAccess soa(env); if (bytes == 0) { return 0; } // bytes is nonnegative and fits into size_t if (!ValidJniSizeArgument(bytes)) { DCHECK(soa.Self()->IsExceptionPending()); return 0; } const size_t malloc_bytes = static_cast<size_t>(bytes); void* mem = malloc(malloc_bytes); if (mem == nullptr) { soa.Self()->ThrowOutOfMemoryError("native alloc"); return 0; } return reinterpret_cast<uintptr_t>(mem); }
Thread 創(chuàng)建
其實(shí)我們java層的Thread創(chuàng)建的時(shí)候,都會(huì)走到native的Thread創(chuàng)建,通過(guò)該方法CreateNativeThread,其實(shí)里面就調(diào)用了傳統(tǒng)的pthread_create去創(chuàng)建一個(gè)native Thread,如果創(chuàng)建失?。ū热缣摂M內(nèi)存不足/FD不足),就會(huì)走到代碼塊中,從而產(chǎn)生OOM
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { ? ?.... ? ? if (pthread_create_result == 0) { ? ? ? // pthread_create started the new thread. The child is now responsible for managing the ? ? ? // JNIEnvExt we created. ? ? ? // Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization ? ? ? // ? ? ? between the threads. ? ? ? child_jni_env_ext.release(); ?// NOLINT pthreads API. ? ? ? return; ? ? } ? } ? // Either JNIEnvExt::Create or pthread_create(3) failed, so clean up. ? { ? ? MutexLock mu(self, *Locks::runtime_shutdown_lock_); ? ? runtime->EndThreadBirth(); ? } ? // Manually delete the global reference since Thread::Init will not have been run. Make sure ? // nothing can observe both opeer and jpeer set at the same time. ? child_thread->DeleteJPeer(env); ? delete child_thread; ? child_thread = nullptr; ? 如果沒(méi)有return,證明失敗了,爆出OOM ? SetNativePeer(env, java_peer, nullptr); ? { ? ? 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()); ? } }
堆內(nèi)存分配
我們平時(shí)采用new 等方法的時(shí)候,其實(shí)進(jìn)入到ART虛擬機(jī)中,其實(shí)是走到Heap::AllocObjectWithAllocator 這個(gè)方法里面,當(dāng)內(nèi)存分配不足的時(shí)候,就會(huì)發(fā)起一次強(qiáng)有力的gc后再嘗試進(jìn)行內(nèi)存分配,這個(gè)方法就是AllocateInternalWithGc
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)
流程如下:
void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) { ? // If we're in a stack overflow, do not create a new exception. It would require running the ? // constructor, which will of course still be in a stack overflow. ? if (self->IsHandlingStackOverflow()) { ? ? self->SetException( ? ? ? ? Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow()); ? ? return; ? }?? 這里官方給了一個(gè)鉤子 ? Runtime::Current()->OutOfMemoryErrorHook(); ? 輸出OOM的原因 ? std::ostringstream oss; ? size_t total_bytes_free = GetFreeMemory(); ? oss << "Failed to allocate a " << byte_count << " byte allocation with " << total_bytes_free ? ? ? << " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << " until OOM," ? ? ? << " target footprint " << target_footprint_.load(std::memory_order_relaxed) ? ? ? << ", growth limit " ? ? ? << growth_limit_; ? // If the allocation failed due to fragmentation, print out the largest continuous allocation. ? if (total_bytes_free >= byte_count) { ? ? space::AllocSpace* space = nullptr; ? ? if (allocator_type == kAllocatorTypeNonMoving) { ? ? ? space = non_moving_space_; ? ? } else if (allocator_type == kAllocatorTypeRosAlloc || ? ? ? ? ? ? ? ?allocator_type == kAllocatorTypeDlMalloc) { ? ? ? space = main_space_; ? ? } else if (allocator_type == kAllocatorTypeBumpPointer || ? ? ? ? ? ? ? ?allocator_type == kAllocatorTypeTLAB) { ? ? ? space = bump_pointer_space_; ? ? } else if (allocator_type == kAllocatorTypeRegion || ? ? ? ? ? ? ? ?allocator_type == kAllocatorTypeRegionTLAB) { ? ? ? space = region_space_; ? ? } ? ? // There is no fragmentation info to log for large-object space. ? ? if (allocator_type != kAllocatorTypeLOS) { ? ? ? CHECK(space != nullptr) << "allocator_type:" << allocator_type ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? << " byte_count:" << byte_count ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? << " total_bytes_free:" << total_bytes_free; ? ? ? // LogFragmentationAllocFailure returns true if byte_count is greater than ? ? ? // the largest free contiguous chunk in the space. Return value false ? ? ? // means that we are throwing OOME because the amount of free heap after ? ? ? // GC is less than kMinFreeHeapAfterGcForAlloc in proportion of the heap-size. ? ? ? // Log an appropriate message in that case. ? ? ? if (!space->LogFragmentationAllocFailure(oss, byte_count)) { ? ? ? ? oss << "; giving up on allocation because <" ? ? ? ? ? ? << kMinFreeHeapAfterGcForAlloc * 100 ? ? ? ? ? ? << "% of heap free after GC."; ? ? ? } ? ? } ? } ? self->ThrowOutOfMemoryError(oss.str().c_str()); }
這個(gè)就是我們常見(jiàn)的,也是主要OOM產(chǎn)生的流程
JNI層
這里還有很多,比如JNI層通過(guò)Env調(diào)用NewString等分配內(nèi)存的時(shí)候,會(huì)進(jìn)入條件檢測(cè),比如分配的String長(zhǎng)度超過(guò)最大時(shí)產(chǎn)生Error,即使說(shuō)內(nèi)存空間依舊可以分配,但是超過(guò)了虛擬機(jī)能處理的最大限制,也會(huì)產(chǎn)生OOM
if (UNLIKELY(utf16_length > static_cast<uint32_t>(std::numeric_limits<int32_t>::max()))) { // Converting the utf16_length to int32_t would overflow. Explicitly throw an OOME. std::string error = android::base::StringPrintf("NewStringUTF input has 2^31 or more characters: %zu", utf16_length); ScopedObjectAccess soa(env); soa.Self()->ThrowOutOfMemoryError(error.c_str()); return nullptr; }
OOM 路徑總結(jié)
通過(guò)本文,我們看到了OOM發(fā)生時(shí),可能存在的幾個(gè)主要路徑,其他引起OOM的路徑,也是在這幾個(gè)基礎(chǔ)路徑之上產(chǎn)生的,希望大家以后可以帶著源碼學(xué)習(xí),能夠幫助我們了解ART更深層的秘密。
到此這篇關(guān)于淺談Java中OutOfMemoryError問(wèn)題產(chǎn)生原因的文章就介紹到這了,更多相關(guān)Java OutOfMemoryError內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java實(shí)戰(zhàn)之OutOfMemoryError異常問(wèn)題及解決方法
- java.lang.OutOfMemoryError: Metaspace異常解決的方法
- 實(shí)例解決Java異常之OutOfMemoryError的問(wèn)題
- Java中內(nèi)存異常StackOverflowError與OutOfMemoryError詳解
- 完美解決java.lang.OutOfMemoryError處理錯(cuò)誤的問(wèn)題
- java.lang.OutOfMemoryError 錯(cuò)誤整理及解決辦法
- 解決Java中OutOfMemoryError的問(wèn)題
相關(guān)文章
SpringBoot讀寫xml上傳到AWS存儲(chǔ)服務(wù)S3的示例
這篇文章主要介紹了SpringBoot讀寫xml上傳到S3的示例,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2020-10-10深入分析RabbitMQ中死信隊(duì)列與死信交換機(jī)
這篇文章主要介紹了RabbitMQ中死信隊(duì)列與死信交換機(jī),死信隊(duì)列就是一個(gè)普通的交換機(jī),有些隊(duì)列的消息成為死信后,一般情況下會(huì)被RabbitMQ清理,感興趣想要詳細(xì)了解可以參考下文2023-05-05Gradle的SpringBoot項(xiàng)目構(gòu)建圖解
這篇文章主要介紹了Gradle的SpringBoot項(xiàng)目構(gòu)建圖解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01SpringBoot整合Mongodb實(shí)現(xiàn)增刪查改的方法
這篇文章主要介紹了SpringBoot整合Mongodb實(shí)現(xiàn)簡(jiǎn)單的增刪查改,MongoDB是一個(gè)以分布式數(shù)據(jù)庫(kù)為核心的數(shù)據(jù)庫(kù),因此高可用性、橫向擴(kuò)展和地理分布是內(nèi)置的,并且易于使用。況且,MongoDB是免費(fèi)的,開(kāi)源的,感興趣的朋友跟隨小編一起看看吧2022-05-05java實(shí)現(xiàn)簡(jiǎn)易飛機(jī)大戰(zhàn)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)易飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Java對(duì)象初始化過(guò)程代碼塊和構(gòu)造器的調(diào)用順序
這篇文章主要介紹了Java對(duì)象初始化過(guò)程代碼塊和構(gòu)造器的調(diào)用順序,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08一文教會(huì)你如何搭建vue+springboot項(xiàng)目
最近在搗鼓?SpringBoot?與?Vue?整合的項(xiàng)目,所以下面這篇文章主要給大家介紹了關(guān)于如何通過(guò)一篇文章教會(huì)你搭建vue+springboot項(xiàng)目,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05Java開(kāi)發(fā)中讀取XML與properties配置文件的方法
這篇文章主要介紹了Java開(kāi)發(fā)中讀取XML與properties配置文件的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01