Java的運行時數據區域

来源:https://www.cnblogs.com/feiyu2/archive/2023/04/02/17279905.html
-Advertisement-
Play Games

本文從概念上介紹 Java 虛擬機記憶體的各個區域,講解這些區域的作用、服務對象以及其中可能產生的問題。 Java 虛擬機在執行 Java 程式的過程中會把它所管理的記憶體劃分為若幹個不同的數據區域。這些區域有各自的用途,以及創建和銷毀的時間,有些區域隨著虛擬機進程的啟動而一直存在,有些區域則是依賴用戶 ...


本文從概念上介紹 Java 虛擬機記憶體的各個區域,講解這些區域的作用、服務對象以及其中可能產生的問題。

Java 虛擬機在執行 Java 程式的過程中會把它所管理的記憶體劃分為若幹個不同的數據區域。這些區域有各自的用途,以及創建和銷毀的時間,有些區域隨著虛擬機進程的啟動而一直存在,有些區域則是依賴用戶線程的啟動和結束而建立和銷毀。

根據《Java 虛擬機規範》的規定, Java 虛擬機所管理的記憶體將會包括以下幾個運行時數據區域:程式計數器、Java 虛擬機棧、本地方法棧、Java 堆、方法區。

image-20230221174739144.png

程式計數器

程式計數器(Program Counter Register)是一塊較小的記憶體空間,程式計數器可以看作是當前線程所執行的位元組碼的行號指示器。在 Java 虛擬機的概念模型里,位元組碼解釋器工作時就是通過改變程式計數器的值來選取下一條需要執行的位元組碼指令,程式計數器是程式控制流的指示器,分支、迴圈、跳轉、異常處理、線程恢復等基礎功能都需要依賴程式計數器來完成。

“概念模型”這個詞會經常被提及,它代表了所有虛擬機的統一外觀,但各款具體的 Java 虛擬機並不一定要完全照著概念模型的定義來進行設計,具體的 Java 虛擬機可能會通過一些更高效率的等價方式去實現它。


由於 Java 虛擬機的多線程是通過線程輪流切換、分配處理器執行時間的方式來實現的,在任何一個確定的時刻, 一個處理器(對於多核處理器來說是一個內核)都只會執行一個線程中的指令。因此,為了線程切換後能恢復到正確的執行位置,每個線程都需要有一個獨立的程式計數器,各個線程之間的程式計數器互不影響,獨立存儲,我們稱這類記憶體區域為 “線程私有” 的記憶體。

如果線程正在執行的是一個 Java 方法, 程式計數器記錄的是正在執行的虛擬機位元組碼指令的地址; 如果線程正在執行的是本地(Native) 方法,程式計數器值則應為空(Undefined)。

程式計數器記憶體區域是唯一一個在《Java 虛擬機規範》中沒有規定任何 OutOfMemoryError 情況的區域。

Java 虛擬機棧

Java 虛擬機棧(Java Virtual Machine Stack)與程式計數器一樣,也是線程私有的記憶體區域,Java 虛擬機棧的生命周期與線程相同。

Java 虛擬機棧描述的是 Java 方法執行的線程記憶體模型:每個方法被執行的時候,Java 虛擬機都會同步創建一個棧幀(Stack Frame)用於存儲局部變數表、操作數棧、動態連接、方法出口等信息。每個方法被調用直至執行完畢的過程,就對應著一個棧幀在 Java 虛擬機棧中從入棧到出棧的過程。

每一個棧幀中分配多少記憶體基本上是在類結構確定下來時就已知的(儘管在運行期會由即時編譯器進行一些優化, 但在基於概念模型的討論里,大體上可以認為是編譯期可知的)


局部變數表

局部變數表存放了編譯期可知的各種 Java 虛擬機基本數據類型(boolean、 byte、 char、 short、 int、float、 long、 double) 、對象引用(reference 類型,對象引用並不等同於對象本身,對象引用可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或者其他與此對象相關的位置)和 returnAddress 類型(指向了一條位元組碼指令的地址)。

這些數據類型在局部變數表中的存儲空間以局部變數槽(Slot)來表示, 其中 64 位長度的 long 和 double 類型的數據會占用兩個變數槽,其餘的數據類型只占用一個變數槽。局部變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時, 這個方法需要在棧幀中分配多大的局部變數空間是完全確定的,在方法運行期間局部變數表的大小不會改變。

請讀者註意,這裡說的 “大小” 指的是變數槽的數量,虛擬機真正使用多大的記憶體空間(譬如按照 1 個變數槽占用 32 個比特、 64 個比特, 或者更多)來實現一個變數槽,這是完全由具體的虛擬機實現自行決定的事情。


在《Java 虛擬機規範》中, 對 Java 虛擬機棧記憶體區域規定了兩類異常狀況:StackOverflowError、OutOfMemoryError

  • 如果線程請求的棧深度大於虛擬機所允許的深度, 將拋出 StackOverflowError 異常(棧深度溢出異常);
  • 如果 Java 虛擬機棧容量可以動態擴展,當棧擴展時無法申請到足夠的記憶體會拋出 OutOfMemoryError 異常。

通過參數 -Xss 來設定單個線程棧的大小,棧的大小直接決定了函數調用的最大深度。

HotSpot 虛擬機的棧容量是不可以動態擴展的,以前的 Classic 虛擬機倒是可以。所以在 HotSpot 虛擬機上是不會由於虛擬機棧無法擴展而導致 OutOfMemoryError 異常。只要線程申請棧空間成功了就不會有 OOM,但是如果線程申請棧空間失敗了,仍然是會出現 OOM 異常的。

本地方法棧

本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用非常相似,它們兩個的區別是:虛擬機棧為虛擬機執行 Java 方法(也就是位元組碼) 服務,而本地方法棧則是為虛擬機使用到的本地(Native)方法服務。

《Java 虛擬機規範》對本地方法棧中方法使用的語言、使用方式與數據結構並沒有任何強制規定,因此具體的虛擬機可以根據需要自由實現它,甚至有的 Java 虛擬機(譬如 HotSpot 虛擬機) 直接就將本地方法棧和虛擬機棧合二為一。

與虛擬機棧一樣,本地方法棧也會在棧深度溢出或者棧擴展失敗時分別拋出 StackOverflowError 和 OutOfMemoryError 異常。

Java 堆

Java 堆是一塊被所有線程共用的記憶體區域,Java 堆在虛擬機啟動時被創建。

Java 堆記憶體區域的唯一目的就是存放對象實例,Java 世界里 “幾乎” 所有的對象實例都在 Java 堆分配記憶體。

在《Java 虛擬機規範》中對 Java 堆的描述是:“所有的對象實例以及數組都應當在堆上分配”,而這裡筆者寫的“幾乎”是指從實現角度來看,隨著 Java 語言的發展,現在已經能看到些許跡象表明日後可能出現值類型的支持,即使只考慮現在,由於即時編譯技術的進步,尤其是逃逸分析技術的日漸強大,棧上分配、標量替換優化手段已經導致一些微妙的變化悄然發生,所以說 Java 對象實例都分配在堆上也漸漸變得不是那麼絕對了。

根據《Java 虛擬機規範》的規定,Java 堆可以處於物理上不連續的記憶體空間中,但在邏輯上它應該被視為連續的,這點就像我們用磁碟空間去存儲文件一樣,並不要求每個文件都連續存放。但對於大對象(典型的如數組對象),多數虛擬機實現出於實現簡單、存儲高效的考慮,很可能會要求連續的記憶體空間。


Java 堆既可以被實現成固定大小的,也可以是可擴展的,不過當前主流的 Java 虛擬機都是按照可擴展來實現的(通過參數 -Xmx 和 -Xms 設定)。 如果 Java 堆無法滿足新的記憶體分配需求,並且堆也無法再擴展時,Java 虛擬機將會拋出 OutOfMemoryError 異常。

固定大小的 Java 堆指的是:只在虛擬機啟動時,向操作系統申請固定大小的堆記憶體空間。

可擴展的 Java 堆指的是:在虛擬機啟動時,向操作系統申請固定大小的初始堆記憶體空間。在空閑的 Java 堆記憶體空間無法滿足新的記憶體分配需求時,再向操作系統申請堆記憶體空間。

方法區

方法區(Method Area)與 Java 堆一樣, 也是被所有線程共用的記憶體區域。

方法區用於存儲已被虛擬機載入的類型信息(如類名、訪問修飾符、欄位描述、方法描述等)、常量、靜態變數、即時編譯器編譯後的代碼緩存等數據。

雖然《Java 虛擬機規範》中把方法區描述為堆的一個邏輯部分,但是方法區它卻有一個別名叫作 “非堆”(Non-Heap) ,目的是與 Java 堆區分開來。

根據《Java 虛擬機規範》的規定,如果方法區無法滿足新的記憶體分配需求時,Java 虛擬機將會拋出 OutOfMemoryError 異常。


《Java 虛擬機規範》對方法區的約束是非常寬鬆的,除了和 Java 堆一樣不需要連續的記憶體和可以選擇固定大小或者可擴展外,甚至還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在方法區這個區域的確是比較少出現的,但並非數據進入了方法區就如永久代的名字一樣 “永久” 存在了。方法區這個區域的記憶體回收目標主要是針對常量池的回收和對類型的卸載, 一般來說方法區這個區域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當苛刻,但是方法區這個區域的回收有時又確實是必要的。 以前 Sun 公司的 Bug 列表中,曾出現過的若幹個嚴重的 Bug 就是由於低版本的 HotSpot 虛擬機對方法區這個區域未完全回收而導致記憶體泄漏。

永久代

說到方法區,不得不提一下 “永久代” 這個概念,尤其是在 JDK8 以前,許多 Java 程式員都習慣在 HotSpot 虛擬機上開發、部署程式,很多人都更願意把方法區稱為 “永久代”(Permanent Generation),或者將這兩者(方法區、永久代)混為一談。本質上這兩者(方法區、永久代)並不是等價的,因為僅僅是當時的 HotSpot 虛擬機設計團隊選擇把垃圾收集器的分代設計擴展至方法區,或者說使用永久代來實現方法區而已, 這樣使得 HotSpot 的垃圾收集器能夠像管理 Java 堆一樣管理方法區這部分記憶體,省去專門為方法區編寫記憶體管理代碼的工作。但是對於其他的虛擬機實現, 譬如 BEA JRockit、IBM J9 等來說,是不存在永久代這個概念的


原則上如何實現方法區屬於虛擬機的實現細節,不受《Java 虛擬機規範》管束, 並不要求統一。但現在回頭來看,當年使用永久代來實現方法區的決定並不是一個好主意,這種設計導致了 Java 應用更容易遇到記憶體溢出的問題(永久代有 -XX:MaxPermSize 的上限,即使不設置也有預設大小,而 J9 和 JRockit 只要沒有觸碰到進程可用記憶體的上限, 例如32位系統中的4GB限制, 就不會出問題) ,而且有極少數方法(例如String::intern()) 會因永久代的原因而導致不同虛擬機下有不同的表現。

當 Oracle 收購 BEA 獲得了JRockit 的所有權後, 準備把 JRockit 中的優秀功能,譬如 Java Mission Control 管理工具, 移植到 HotSpot 虛擬機時,但因為兩者對方法區實現的差異而面臨諸多困難。

考慮到 HotSpot 未來的發展,在 JDK6 的時候 HotSpot 開發團隊就有放棄永久代,逐步改為採用本地記憶體(Native Memory)來實現方法區的計划了,到了 JDK7 的 HotSpot,已經把原本放在永久代的字元串常量池、靜態變數等移出, 而到了 JDK8 , 終於完全廢棄了永久代的概念, 改用與 JRockit、J9 一樣在本地記憶體中實現的元空間(Metaspace)來代替,把 JDK7 中永久代還剩餘的內容(主要是類型信息) 全部移到元空間中。

運行時常量池

運行時常量池(Runtime Constant Pool)是方法區的一部分。

Class 文件中除了有類的版本、欄位、方法、介面等描述信息外,還有一項信息是常量池表(Constant Pool Table),常量池表用於存放編譯期生成的各種字面量與符號引用,這部分內容將在類載入後存放到方法區的運行時常量池中。

Java 虛擬機對於 Class 文件每一部分(自然也包括常量池)的格式都有嚴格規定,如每一個位元組用於存儲哪種數據都必須符合規範上的要求才會被虛擬機認可、載入和執行,但對於運行時常量池,《Java 虛擬機規範》並沒有做任何細節的要求,不同提供商實現的虛擬機可以按照自己的需要來實現這個記憶體區域,不過一般來說,除了保存 Class 文件中描述的符號引用外,還會把由符號引用翻譯出來的直接引用也存儲在運行時常量池中。

運行時常量池相對於 Class 文件常量池的另外一個重要特征是具備動態性,Java 語言並不要求常量一定只有編譯期才能產生,也就是說,並非預置入 Class 文件中常量池的內容才能進入方法區的運行時常量池, 運行期間也可以將新的常量放入池中,這種特性被開發人員利用得比較多的便是 String 類的 intern() 方法。

直接記憶體

直接記憶體(Direct Memory)並不是虛擬機運行時數據區域的一部分,也不是《Java 虛擬機規範》中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,而且也可能導致 OutOfMemoryError 異常出現,所以我們放到這裡一起講解。

在 JDK1.4 中新加入了 NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式, NIO 它可以使用 Native 函數庫直接分配堆外記憶體,然後通過一個存儲在 Java 堆裡面的 DirectByteBuffer 對象作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在 Java 堆和 Native 堆中來回覆制數據。

顯然,本機直接記憶體的分配不會受到 Java 堆大小的限制,但是,既然是記憶體,則肯定還是會受到本機總記憶體(包括物理記憶體、SWAP 分區或者分頁文件)大小以及處理器定址空間的限制,一般伺服器管理員配置虛擬機參數時,會根據實際記憶體去設置 -Xmx 等參數信息,但經常忽略掉直接記憶體,使得各個記憶體區域總和大於物理記憶體限制(包括物理的和操作系統級的限制),從而導致動態擴展時出現 OutOfMemoryError 異常。

總結

運行時數據區域

程式計數器

程式計數器是一塊較小的記憶體空間。程式計數器是“線程私有”的數據區域。

如果一個線程正在執行的是一個 Java 方法, 程式計數器記錄的是正在執行的虛擬機位元組碼指令的地址。

在 Java 虛擬機的概念模型里,位元組碼解釋器工作時就是通過改變程式計數器的值來選取下一條需要執行的位元組碼指令,程式計數器是程式控制流的指示器,分支、迴圈、跳轉、異常處理、線程恢復等基礎功能都需要依賴程式計數器來完成。


Java 虛擬機棧、本地方法棧

HotSpot 虛擬機將本地方法棧和虛擬機棧合二為一。

  • Java 虛擬機棧描述的是 Java 方法執行的線程記憶體模型:每個方法被執行的時候,Java 虛擬機都會同步創建一個棧幀(Stack Frame)用於存儲局部變數表、操作數棧、動態連接、方法出口等信息。每個方法被調用直至執行完畢的過程,就對應著一個棧幀在 Java 虛擬機棧中從入棧到出棧的過程。
  • 本地方法棧(Native Method Stacks) 與虛擬機棧所發揮的作用非常相似,它們兩個的區別是:虛擬機棧為虛擬機執行 Java 方法(也就是位元組碼) 服務,而本地方法棧則是為虛擬機使用到的本地(Native)方法服務。

【Java 堆】記憶體區域的唯一目的就是存放對象實例,Java 世界里 “幾乎” 所有的對象實例都在【Java 堆】區域分配記憶體。

【方法區】記憶體區域用於存儲已被虛擬機載入的類型信息(如類名、訪問修飾符、欄位描述、方法描述等)、常量、靜態變數、即時編譯器編譯後的代碼緩存等數據。

“線程私有” 的區域

“線程私有” 的記憶體區域:每個線程都有一個獨立的記憶體區域,各個線程之間的記憶體區域互不影響, 獨立存儲,我們稱這類記憶體區域為 “線程私有” 的記憶體區域。

  • “線程私有” 的記憶體區域有:程式計數器、Java 虛擬機棧、本地方法棧;
  • 被所有線程共用的記憶體區域有:Java 堆、方法區。

垃圾收集的區域

程式計數器、Java 虛擬機棧、本地方法棧這三個運行時數據區域隨線程而生,隨線程而滅,棧中的棧幀隨著方法的進入和退出而有條不紊地執行著入棧和出棧操作。每一個棧幀中分配多少記憶體基本上是在類結構確定下來時就已知的(儘管在運行期會由即時編譯器進行一些優化,但在基於概念模型的討論里,大體上可以認為是編譯期可知的),因此這三個運行時數據區域的記憶體分配和回收都具備確定性,在這三個運行時數據區域內就不需要過多考慮如何回收的問題,當方法結束或者線程結束時,記憶體自然就跟隨著回收了。

而 Java 堆和方法區這兩個運行時數據區域則有著很顯著的不確定性:一個介面的多個實現類需要的記憶體可能會不一樣, 一個方法所執行的不同條件分支所需要的記憶體也可能不一樣,只有處於運行期間,我們才能知道程式究竟會創建哪些對象,創建多少個對象,這部分(Java 堆、方法區)記憶體的分配和回收是動態的。垃圾收集器所關註的正是這部分(Java 堆、方法區)記憶體該如何管理。

記憶體區域的異常狀況

【程式計數器】記憶體區域是唯一一個在《Java 虛擬機規範》中沒有規定任何 OutOfMemoryError 情況的區域。


【Java 虛擬機棧】、【本地方法棧】記憶體區域:在【Java 虛擬機棧】、【本地方法棧】記憶體區域中,可能出現的異常狀況有:OutOfMemoryError、StackOverflowError:

  • 創建線程時,需要申請棧空間。如果線程申請棧空間失敗了,那麼 Java 虛擬機就會拋出 OutOfMemoryError 異常。
  • 線程申請棧空間成功後,如果線程請求的棧深度大於虛擬機所允許的深度,那麼 Java 虛擬機就會拋出 StackOverflowError 異常。

【Java 堆】記憶體區域:如果 Java 堆無法滿足新的記憶體分配需求,並且堆也無法再擴展時,Java 虛擬機將會拋出 OutOfMemoryError 異常。

【方法區】記憶體區域:如果方法區無法滿足新的記憶體分配需求時,Java 虛擬機將會拋出 OutOfMemoryError 異常。

【直接記憶體】:如果各個記憶體區域的總和大於物理記憶體限制(包括物理的和操作系統級的限制),Java 虛擬機將會拋出 OutOfMemoryError 異常。

參考資料

《深入理解 Java 虛擬機》第 2 章:Java 記憶體區域與記憶體溢出異常 2.2 運行時數據區域

本文來自博客園,作者:真正的飛魚,轉載請註明原文鏈接:https://www.cnblogs.com/feiyu2/p/17279905.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述 背景 函數式編程的理論基礎是阿隆佐·丘奇(Alonzo Church)於 1930 年代提出的 λ 演算(Lambda Calculus)。λ 演算是一種形式系統,用於研究函數定義、函數應用和遞歸。它為計算理論和電腦科學的發展奠定了基礎。隨著 Haskell(1990年)和 Erlang(1 ...
  • 一門語言教程被搜索的次數越多,大家就會認為該語言越受歡迎。這是一個領先指標。原始數據來自谷歌Trends 如果您相信集體智慧,那麼流行編程語言排名可以幫助您決定學習哪門語言,或者在一個新的軟體項目中使用哪一門語言 ...
  • 請編寫一個程式,使用兩個線程分別輸出數字和字母,要求輸出的結果為:1A2B3C4D5E6F7G8H9I10J。 提示:可以使用Java中的wait()和notify()方法來實現線程間的通信。 public class NumberLetterPrinter { // 定義一個靜態的鎖對象 priv ...
  • 原文鏈接: Go 語言數組和切片的區別 在 Go 語言中,數組和切片看起來很像,但其實它們又有很多的不同之處,這篇文章就來說說它們到底有哪些不同。 另外,這個問題在面試中也經常會被問到,屬於入門級題目,看過文章之後,相信你會有一個很好的答案。 數組 數組是同一種數據類型元素的集合,數組在定義時需要指 ...
  • 流程式控制制 選擇結構(分支語句) ​ 因為switch只能匹配固定值,推薦使用if-else做條件篩選 if-else判斷 package main import "fmt" func main() { var tmpA int fmt.Scanln(&tmpA) if tmpA >= 90 { fm ...
  • 一 》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》 下載nacos https://github.com/alibaba/nacos nacos-server-2.0.3.zip Windows 版 解壓後,資料庫新建nacos庫,將 X:\nacos\ ...
  • 本文主要介紹在 Tomcat 集群中如何進行 Session 複製,文中所使用到的軟體版本:Centos 7.9.2009、Java 1.8.0_321、Tomcat 8.5.87。 1、快速配置 取消 conf/server.xml 文件中的以下註釋來啟用集群: <Cluster classNam ...
  • 作者最近嘗試寫了一些Rust代碼,本文主要講述了對Rust的看法和Rust與C++的一些區別。 背景 S2在推進團隊代碼規範時,先後學習了盤古編程規範,CPP core guidelines,進而瞭解到clang-tidy,以及Google Chrome 在安全方面的探索。 C++是一個威力非常強大 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...