淺談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-05
Gradle的SpringBoot項(xiàng)目構(gòu)建圖解
這篇文章主要介紹了Gradle的SpringBoot項(xiàng)目構(gòu)建圖解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
SpringBoot整合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-05
java實(shí)現(xiàn)簡(jiǎn)易飛機(jī)大戰(zhàn)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)易飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Java對(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-05
Java開(kāi)發(fā)中讀取XML與properties配置文件的方法
這篇文章主要介紹了Java開(kāi)發(fā)中讀取XML與properties配置文件的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01

