Android運行時ART加載類和方法的過程分析

目錄

一,概述

二,ART運行時的入口


一,概述

既然ART運行時執行的都是翻譯DEX字節碼后得到的本地機器指令了,為什么還需要在OAT文件中包含DEX文件,并且將它加載到內存去呢?這是因為ART運行時提供了Java虛擬機接口,而要實現Java虛擬機接口不得不依賴于DEX文件

ART運行時查找類方法的本地機器指令的過程

為了方便描述,我們將DEX文件中描述的類和方法稱為DEX類(Dex Class)和DEX方法(Dex Method),而將在OAT文件中描述的類和方法稱為OAT類(Oat Class)和OAT方法(Oat Method)。接下來我們還會看到,ART運行時在內部又會使用另外兩個不同的術語來描述類和方法,其中將類描述為Class,而將類方法描述為ArtMethod。

? ? ? ?在圖1中,為了找到一個類方法的本地機器指令,我們需要執行以下的操作:

? ? ? ?1. 在DEX文件中找到目標DEX類的編號,并且以這個編號為索引,在OAT文件中找到對應的OAT類。

? ? ? ?2. 在DEX文件中找到目標DEX方法的編號,并且以這個編號為索引,在上一步找到的OAT類中找到對應的OAT方法。

? ? ? ?3. 使用上一步找到的OAT方法的成員變量begin_和code_offset_,計算出該方法對應的本地機器指令

二,ART運行時的入口

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{ALOGD(">>>>>> START %s uid %d <<<<<<\n",className != NULL ? className : "(unknown)", getuid());static const String8 startSystemServer("start-system-server");/** 'startSystemServer == true' means runtime is obsolete and not run from* init.rc anymore, so we print out the boot start event here.*/for (size_t i = 0; i < options.size(); ++i) {if (options[i] == startSystemServer) {/* track our progress through the boot sequence */const int LOG_BOOT_PROGRESS_START = 3000;LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));}}const char* rootDir = getenv("ANDROID_ROOT");if (rootDir == NULL) {rootDir = "/system";if (!hasDir("/system")) {LOG_FATAL("No root directory specified, and /android does not exist.");return;}setenv("ANDROID_ROOT", rootDir, 1);}//const char* kernelHack = getenv("LD_ASSUME_KERNEL");//ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);/* start the virtual machine */JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;if (startVm(&mJavaVM, &env, zygote) != 0) {return;}onVmCreated(env);/** Register android functions.*/if (startReg(env) < 0) {‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’
}

首先是通過調用函數startVm創建了一個Java虛擬機mJavaVM及其JNI接口env。這個Java虛擬機實際上就是ART運行時。在接下來的描述中,我們將不區分ART虛擬機和ART運行時,并且認為它們表達的是同一個概念。獲得了ART虛擬機的JNI接口之后,就可以通過它提供的函數FindClass和GetStaticMethodID來加載com.android.internal.os.ZygoteInit類及其靜態成員函數main。于是,最后就可以再通過JNI接口提供的函數CallStaticVoidMethod來調用com.android.internal.os.ZygoteInit類的靜態成員函數main,以及進行到ART虛擬機里面去運行。
?在分析JNI接口FindClass和GetStaticMethodID的實現之前,我們先要講清楚JNI接口是如何創建的

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {return JniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm, p_env, vm_args);
}

JNI類的靜態成員函數FindClass的實現如下所示

/Volumes/aosp/android-8.1.0_r52/art/runtime/jni_internal.cc

static jclass FindClass(JNIEnv* env, const char* name) {CHECK_NON_NULL_ARGUMENT(name);Runtime* runtime = Runtime::Current();ClassLinker* class_linker = runtime->GetClassLinker();std::string descriptor(NormalizeJniClassDescriptor(name));ScopedObjectAccess soa(env);mirror::Class* c = nullptr;if (runtime->IsStarted()) {StackHandleScope<1> hs(soa.Self());Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);} else {c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());}return soa.AddLocalReference<jclass>(c);}

NI類的靜態成員函數FindClass首先是判斷ART運行時是否已經啟動起來。如果已經啟動,那么就通過調用函數GetClassLoader來獲得當前線程所關聯的ClassLoader,并且以此為參數,調用前面獲得的ClassLinker對象的成員函數FindClass來加載由參數name指定的類。一般來說,當前線程所關聯的ClassLoader就是當前正在執行的類方法所關聯的ClassLoader,即用來加載當前正在執行的類的ClassLoader。如果ART虛擬機還沒有開始執行類方法,就像我們現在這個場景,那么當前線程所關聯的ClassLoader實際上就系統類加載器,即SystemClassLoader。

? ? ? ?如果ART運行時還沒有啟動,那么這時候只可以加載系統類。這個通過前面獲得的ClassLinker對象的成員函數FindSystemClass來實現的。在我們這個場景中,ART運行時已經啟動,因此,接下來我們就繼續分析ClassLinker類的成員函數FindClass的實現。

? ? ? ? ClassLinker類的成員函數FindClass的實現如下所示:


?/Volumes/aosp/android-8.1.0_r52/art/runtime/class_linker.cc

mirror::Class* ClassLinker::FindClass(Thread* self,const char* descriptor,Handle<mirror::ClassLoader> class_loader) {DCHECK_NE(*descriptor, '\0') << "descriptor is empty string";DCHECK(self != nullptr);self->AssertNoPendingException();self->PoisonObjectPointers();  // For DefineClass, CreateArrayClass, etc...if (descriptor[1] == '\0') {// only the descriptors of primitive types should be 1 character long, also avoid class lookup// for primitive classes that aren't backed by dex files.return FindPrimitiveClass(descriptor[0]);}const size_t hash = ComputeModifiedUtf8Hash(descriptor);// Find the class in the loaded classes table.ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get());if (klass != nullptr) {return EnsureResolved(self, descriptor, klass);}// Class is not yet loaded.if (descriptor[0] != '[' && class_loader == nullptr) {// Non-array class and the boot class loader, search the boot class path.ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);if (pair.second != nullptr) {return DefineClass(self,descriptor,hash,ScopedNullHandle<mirror::ClassLoader>(),*pair.first,*pair.second);} else {// The boot class loader is searched ahead of the application class loader, failures are// expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to// trigger the chaining with a proper stack trace.ObjPtr<mirror::Throwable> pre_allocated =Runtime::Current()->GetPreAllocatedNoClassDefFoundError();self->SetException(pre_allocated);return nullptr;}}ObjPtr<mirror::Class> result_ptr;bool descriptor_equals;if (descriptor[0] == '[') {result_ptr = CreateArrayClass(self, descriptor, hash, class_loader);DCHECK_EQ(result_ptr == nullptr, self->IsExceptionPending());DCHECK(result_ptr == nullptr || result_ptr->DescriptorEquals(descriptor));descriptor_equals = true;} else {ScopedObjectAccessUnchecked soa(self);bool known_hierarchy =FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result_ptr);if (result_ptr != nullptr) {// The chain was understood and we found the class. We still need to add the class to// the class table to protect from racy programs that can try and redefine the path list// which would change the Class<?> returned for subsequent evaluation of const-class.DCHECK(known_hierarchy);DCHECK(result_ptr->DescriptorEquals(descriptor));descriptor_equals = true;} else {// Either the chain wasn't understood or the class wasn't found.//// If the chain was understood but we did not find the class, let the Java-side// rediscover all this and throw the exception with the right stack trace. Note that// the Java-side could still succeed for racy programs if another thread is actively// modifying the class loader's path list.if (!self->CanCallIntoJava()) {// Oops, we can't call into java so we can't run actual class-loader code.// This is true for e.g. for the compiler (jit or aot).ObjPtr<mirror::Throwable> pre_allocated =Runtime::Current()->GetPreAllocatedNoClassDefFoundError();self->SetException(pre_allocated);return nullptr;}
''''''''''''''''''''''''''''''''''''''''''''''''''''''''
}

ClassLinker類的成員函數FindClass首先是調用另外一個成員函數LookupClass來檢查參數descriptor指定的類是否已經被加載過。如果是的話,那么ClassLinker類的成員函數LookupClass就會返回一個對應的Class對象,這個Class對象接著就會返回給調用者,表示加載已經完成
?

知道了參數descriptor指定的類定義在哪一個DEX文件之后,就可以通過ClassLinker類的另外一個成員函數DefineClass來從中加載它了。接下來,我們就繼續分析ClassLinker類的成員函數DefineClass的實現

mirror::Class* ClassLinker::DefineClass(Thread* self,const char* descriptor,size_t hash,Handle<mirror::ClassLoader> class_loader,const DexFile& dex_file,const DexFile::ClassDef& dex_class_def) {StackHandleScope<3> hs(self);auto klass = hs.NewHandle<mirror::Class>(nullptr);// Load the class from the dex file.if (UNLIKELY(!init_done_)) {// finish up init of hand crafted class_roots_if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {klass.Assign(GetClassRoot(kJavaLangObject));} else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {klass.Assign(GetClassRoot(kJavaLangClass));} else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {klass.Assign(GetClassRoot(kJavaLangString));} else if (strcmp(descriptor, "Ljava/lang/ref/Reference;") == 0) {klass.Assign(GetClassRoot(kJavaLangRefReference));} else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {klass.Assign(GetClassRoot(kJavaLangDexCache));} else if (strcmp(descriptor, "Ldalvik/system/ClassExt;") == 0) {klass.Assign(GetClassRoot(kDalvikSystemClassExt));}}if (klass == nullptr) {// Allocate a class with the status of not ready.// Interface object should get the right size here. Regular class will// figure out the right size later and be replaced with one of the right// size when the class becomes resolved.klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));}if (UNLIKELY(klass == nullptr)) {self->AssertPendingOOMException();return nullptr;}

調用ClassLinker類的成員函數DefineClass的時候,如果ClassLinker正處于初始化過程,即其成員變量init_done_的值等于false,并且參數descriptor描述的是特定的內部類,那么就將本地變量klass指向它們,其余情況則會通過成員函數AllocClass為其分配存儲空間,以便后面通過成員函數LoadClass進行初始化
?

接下來,我們主要分析ClassLinker類的成員函數LoadClass的實現,以便可以了解類的加載過程。

??????? ClassLinker類的成員函數LoadClass的實現如下所示

void ClassLinker::LoadClass(Thread* self,const DexFile& dex_file,const DexFile::ClassDef& dex_class_def,Handle<mirror::Class> klass) {const uint8_t* class_data = dex_file.GetClassData(dex_class_def);if (class_data == nullptr) {return;  // no fields or methods - for example a marker interface}LoadClassMembers(self, dex_file, class_data, klass);
}
void ClassLinker::LoadClassMembers(Thread* self,const DexFile& dex_file,const uint8_t* class_data,Handle<mirror::Class> klass) {{// Note: We cannot have thread suspension until the field and method arrays are setup or else// Class::VisitFieldRoots may miss some fields or methods.ScopedAssertNoThreadSuspension nts(__FUNCTION__);// Load static fields.// We allow duplicate definitions of the same field in a class_data_item// but ignore the repeated indexes here, b/21868015.LinearAlloc* const allocator = GetAllocatorForClassLoader(klass->GetClassLoader());ClassDataItemIterator it(dex_file, class_data);LengthPrefixedArray<ArtField>* sfields = AllocArtFieldArray(self,allocator,it.NumStaticFields());size_t num_sfields = 0;uint32_t last_field_idx = 0u;for (; it.HasNextStaticField(); it.Next()) {uint32_t field_idx = it.GetMemberIndex();DCHECK_GE(field_idx, last_field_idx);  // Ordering enforced by DexFileVerifier.if (num_sfields == 0 || LIKELY(field_idx > last_field_idx)) {DCHECK_LT(num_sfields, it.NumStaticFields());LoadField(it, klass, &sfields->At(num_sfields));++num_sfields;last_field_idx = field_idx;}}// Load instance fields.LengthPrefixedArray<ArtField>* ifields = AllocArtFieldArray(self,allocator,it.NumInstanceFields());size_t num_ifields = 0u;last_field_idx = 0u;for (; it.HasNextInstanceField(); it.Next()) {uint32_t field_idx = it.GetMemberIndex();DCHECK_GE(field_idx, last_field_idx);  // Ordering enforced by DexFileVerifier.if (num_ifields == 0 || LIKELY(field_idx > last_field_idx)) {DCHECK_LT(num_ifields, it.NumInstanceFields());LoadField(it, klass, &ifields->At(num_ifields));++num_ifields;last_field_idx = field_idx;}}
'''''''''''''''
}

們首先要明確一下各個參數的含義:

? ? ? ?dex_file: 類型為DexFile,描述要加載的類所在的DEX文件。

? ? ? ?dex_class_def: 類型為ClassDef,描述要加載的類在DEX文件里面的信息。

? ? ? ?klass: 類型為Class,描述加載完成的類。

? ? ? ?class_loader: ?類型為ClassLoader,描述所使用的類加載器
?ClassLinker類的成員函數LoadClass的任務就是要用dex_file、dex_class_def、class_loader三個參數包含的相關信息設置到參數klass描述的Class對象去,以便可以得到一個完整的已加載類信息。

static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,const char* name, const char* sig, bool is_static)REQUIRES_SHARED(Locks::mutator_lock_) {ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));if (c == nullptr) {return nullptr;}ArtMethod* method = nullptr;auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();if (c->IsInterface()) {method = c->FindInterfaceMethod(name, sig, pointer_size);} else {method = c->FindClassMethod(name, sig, pointer_size);}if (method == nullptr || method->IsStatic() != is_static) {ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");return nullptr;}return jni::EncodeArtMethod(method);
}

函數FindMethodID的執行過程如下所示:

? ? ? ?1. 將參數jni_class的值轉換為一個Class指針c,因此就可以得到一個Class對象,并且通過ClassLinker類的成員函數EnsureInitialized確保該Class對象描述的類已經初始化。

? ? ? ?2. Class對象c描述的類在加載的過程中,經過解析已經關聯上一系列的成員函數。這些成員函數可以分為兩類:Direct和Virtual。Direct類的成員函數包括所有的靜態成員函數、私有成員函數和構造函數,而Virtual則包括所有的虛成員函數。因此:

? ? ? ? ? ?2.1. 當參數is_static的值等于true時,那么就表示要查找的是靜態成員函數,這時候就在Class對象c描述的類的關聯的Direct成員函數列表中查找參數name和sig對應的成員函數。這是通過調用Class類的成員函數FindDirectMethod來實現的。

? ? ? ? ? ?2.2. 當參數is_static的值不等于true時,那么就表示要查找的是虛擬成員函數或者非靜態的Direct成員函數,這時候先在Class對象c描述的類的關聯的Virtual成員函數列表中查找參數name和sig對應的成員函數。這是通過調用Class類的成員函數FindVirtualMethod來實現的。如果找不到對應的虛擬成員函數,那么再在Class對象c描述的類的關聯的Direct成員函數列表中查找參數name和sig對應的成員函數。

? ? ? ?3. 經過前面的查找過程,如果都不能在Class對象c描述的類中找到與參數name和sig對應的成員函數,那么就拋出一個NoSuchMethodError異常。否則的話,就將查找得到的ArtMethod對象封裝成一個jmethodID值返回給調用者。

? ? ? ?也就是說,我們通過調用JNI接口GetStaticMethodID獲得的不透明jmethodID值指向的實際上是一個ArtMethod對象。得益于前面的類加載過程,當我們獲得了一個ArtMethod對象之后,就可以輕松地得到它的本地機器指令入口,進而對它進行執行
?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79459.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79459.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79459.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Javase 基礎加強 —— 02 泛型

本系列為筆者學習Javase的課堂筆記&#xff0c;視頻資源為B站黑馬程序員出品的《黑馬程序員JavaAI智能輔助編程全套視頻教程&#xff0c;java零基礎入門到大牛一套通關》&#xff0c;章節分布參考視頻教程&#xff0c;為同樣學習Javase系列課程的同學們提供參考。 01 認識泛型…

Oracle VirtualBox 在 macOS 上的詳細安裝步驟

Oracle VirtualBox 在 macOS 上的詳細安裝步驟 一、準備工作1. 系統要求2. 下載安裝包二、安裝 VirtualBox1. 掛載安裝鏡像2. 運行安裝程序3. 處理安全限制(僅限首次安裝)三、安裝擴展包(增強功能)四、配置第一個虛擬機1. 創建新虛擬機2. 分配內存3. 創建虛擬硬盤4. 加載系…

RAGFlow 接入企業微信應用實現原理剖析與最佳實踐

背景 近期有醫美行業客戶咨詢我們智能客服產品&#xff0c;期望將自己企業的產品、服務以及報價信息以企微應用的方式給到客戶進行體驗互動&#xff0c;提升企業運營效率。關于企業微信對接&#xff0c;我們分享下最佳實踐&#xff0c;拋磚引玉。效果圖如下&#xff1a; 這里也…

【心海資源】子比主題新增注冊與會員用戶展示功能模塊及實現方法

內容改寫&#xff1a; 本次分享的是子比主題頂部展示注冊用戶與會員信息的功能模塊及其實現方式。 你可以通過兩種方式啟用該功能&#xff1a; 直接在后臺進入“外觀 → 小工具”啟用該展示模塊&#xff0c;操作簡便&#xff1b;也可將提供的代碼覆蓋至子比主題目錄中&#…

CSDN積分詳解(介紹、獲取、用途)

&#x1f91f;致敬讀者 &#x1f7e9;感謝閱讀&#x1f7e6;笑口常開&#x1f7ea;生日快樂?早點睡覺 &#x1f4d8;博主相關 &#x1f7e7;博主信息&#x1f7e8;博客首頁&#x1f7eb;專欄推薦&#x1f7e5;活動信息 文章目錄 積分**一、積分類型及用途****二、積分獲取途…

【iview】es6變量結構賦值(對象賦值)

變量的解構賦值 以iview的src/index.js中Vue.prototype.$IVIEW改造為例練習下怎么使用變量的解構賦值 原來的寫法&#xff1a; const install function(Vue, opts {}) {if (install.installed) return;locale.use(opts.locale);locale.i18n(opts.i18n);Object.keys(iview).fo…

【c++深入系列】:萬字詳解vector(附模擬實現的vector源碼)

&#x1f525; 本文專欄&#xff1a;c &#x1f338;作者主頁&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客勵志語錄&#xff1a; 種子破土時從不問‘會不會有光’&#xff0c;它只管生長 ★★★ 本文前置知識&#xff1a; 模版 1.什么是vector 那么想必大家都學過順…

MySQL基礎關鍵_007_DQL 練習

目 錄 一、題目 二、答案&#xff08;不唯一&#xff09; 1.查詢每個部門薪資最高的員工信息 2.查詢每個部門高于平均薪水的員工信息 3. 查詢每個部門平均薪資等級 4.查詢部門中所有員工薪資等級的平均等級 5.不用分組函數 max 查詢最高薪資 6.查詢平均薪資最高的部門編…

Jenkis安裝、配置及賬號權限分配保姆級教程

Jenkis安裝、配置及賬號權限分配保姆級教程 安裝Jenkins下載Jenkins啟動Jenkins配置Jenkins入門Jenkins配置配置中文配置前端自動化任務流新建任務拉取代碼打包上傳云服務并運行配置后端自動化任務流新建任務拉取代碼打包上傳云服務并運行賬號權限分配創建用戶分配視圖權限安裝…

虛函數 vs 純虛函數 vs 靜態函數(C++)

&#x1f9e9; 一圖看懂&#xff1a;虛函數 vs 純虛函數 特性虛函數&#xff08;Virtual&#xff09;純虛函數&#xff08;Pure Virtual&#xff09;語法virtual void foo();virtual void foo() 0;是否必須實現? 必須在類中實現? 不在基類實現&#xff0c;派生類必須實現是…

2025年滲透測試面試題總結-拷打題庫36(題目+回答)

網絡安全領域各種資源&#xff0c;學習文檔&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各種好玩的項目及好用的工具&#xff0c;歡迎關注。 目錄 2025年滲透測試面試題總結-拷打題庫36 PHP代碼常見入口函數查找 PHP框架路由方法熟悉度 PHP變量覆蓋…

STL之vector容器

vector的介紹 1.vector是可變大小數組的容器 2.像數組一樣&#xff0c;采用連續的空間存儲&#xff0c;也就意味著可以通過下標去訪問&#xff0c;但它的大小可以動態改變 3.每次的插入都要開空間嗎&#xff1f;開空間就要意味著先開臨時空間&#xff0c;然后在拷貝舊的到新…

[學成在線]22-自動部署項目

自動部署 實戰流程 下邊使用jenkins實現CI/CD的流程。 1、將代碼使用Git托管 2、在jenkins創建任務&#xff0c;從Git拉取代碼。 3、拉取代碼后進行自動構建&#xff1a;測試、打包、部署。 首先將代碼打成鏡像包上傳到docker私服。 自動創建容器、啟動容器。 4、當有代…

74HC123的電路應用場景

74HC123的電路應用場景 **1. 引腳功能示例****2. 核心功能****&#xff08;1&#xff09;單穩態觸發器****&#xff08;2&#xff09;雙獨立通道****&#xff08;3&#xff09;靈活觸發方式** **3. 工作原理****4. 典型應用場景****&#xff08;1&#xff09;定時與延時控制***…

【人工智能】大模型安全的深度剖析:DeepSeek漏洞分析與防護實踐

《Python OpenCV從菜鳥到高手》帶你進入圖像處理與計算機視覺的大門! 解鎖Python編程的無限可能:《奇妙的Python》帶你漫游代碼世界 隨著大語言模型(LLM)的廣泛應用,其安全性問題日益凸顯。DeepSeek作為中國領先的開源AI模型,以低成本和高性能著稱,但近期暴露的數據庫…

《ESP32音頻開發實戰:I2S協議解析與WAV音頻錄制/播放全指南》

前言 在智能硬件和物聯網應用中&#xff0c;音頻處理能力正成為越來越重要的功能——無論是語音交互、環境音采集&#xff0c;還是音樂播放&#xff0c;都離不開高效的音頻數據傳輸與處理。而I2S&#xff08;Inter-IC Sound&#xff09;作為專為音頻設計的通信協議&#xff0c…

大數據實時數倉的數據質量監控解決方案

實時數倉不僅僅是傳統數據倉庫的升級版,它更強調數據的實時性、流動性和高可用性,通過對海量數據的即時處理和分析,為企業提供近乎實時的洞察力。這種能力在金融、零售、制造、互聯網等行業中尤為關鍵,例如,電商平臺可以通過實時數倉監控用戶行為,動態調整推薦算法;金融…

56認知干貨:智能化產業

如果在不久的未來,一座高樓大廈的建設,只需將圖紙輸入系統,無數臺機器人就能精準協作完成任務; 電影節的主角不再是人類,動漫與影視作品將不再需要人類創作; 當播種和收獲的工作無人參與,所有過程都能自動化進行; 這將預示著我們將迎來一個智能化社會,在這個社會中,…

使用synchronized關鍵字同步Java線程

問題 在Java多線程編程中&#xff0c;你需要保護某些數據&#xff0c;防止多個線程同時訪問導致數據不一致或程序錯誤。 解決方案 在需要保護的方法或代碼段上使用synchronized關鍵字。 討論 synchronized關鍵字是Java提供的同步機制&#xff0c;用于確保在同一時刻只有一…

MATLAB基于格拉姆角場與2DCNN-BiGRU的軸承故障診斷模型

本博客來源于CSDN機器魚&#xff0c;未同意任何人轉載。 更多內容&#xff0c;歡迎點擊本專欄目錄&#xff0c;查看更多內容。 目錄 0 引言 1 格拉姆角場原理 2 2DCNN-BiGRU網絡結構 3 應用實例 3.1 數據準備 3.2 格拉姆角場數據提取 3.3 網絡模型搭建-重中之重 3.4 …