java對象的記憶體佈局

来源:https://www.cnblogs.com/star95/archive/2023/07/03/17522142.html
-Advertisement-
Play Games

寫java時不管是我們自己new對象還是spring管理bean,儘管我們天天跟對象打交道,那麼對象的結構和記憶體佈局有多少人知道呢,這篇文章可帶你入門,瞭解java對象記憶體佈局。 本文涉及到JVM指針壓縮的知識點,不熟悉的小伙伴可以看前面寫過的一篇關於指針壓縮的文章。 [JVM之指針壓縮](http ...


寫java時不管是我們自己new對象還是spring管理bean,儘管我們天天跟對象打交道,那麼對象的結構和記憶體佈局有多少人知道呢,這篇文章可帶你入門,瞭解java對象記憶體佈局。
本文涉及到JVM指針壓縮的知識點,不熟悉的小伙伴可以看前面寫過的一篇關於指針壓縮的文章。
JVM之指針壓縮

首先說明,本文涉及的JDK版本是1.8,JVM虛擬機是64位的HotSpot實現為準。

java對象結構

關於java對象我們知道, 對象的實例是存在堆上的, 對象的元數據存在方法區(元空間)上,對象的引用保存在棧上的。那麼java對象的結構是什麼樣的呢,其實java對象由三部分構成。

  1. 對象頭

對象頭裡也有三部分構成。

  • Markword

存儲對象的hashCode、垃圾回收對象的年齡以及鎖信息等。

  • 類型指針

對象指向的類信息地址即元數據指針,比如User對象指針指向User.class的JVM記憶體地址。註意:jdk1.8以後元數據是存在Metaspace里的,jdk1.8之前是在方法區里

  • 數組長度

只有對象是數組的情況下,才有這部分數據,若對象不是數組,則沒有這部分,不分配空間。

  1. 對象體

對象里的非靜態屬性占用的空間(包括父類的所有屬性,不區分修飾類型),不包括方法,註意:是非靜態屬性,屬於對象的屬性,靜態屬性是屬於類的不在對象上分配空間。如果屬性是基本數據類型,則直接存對象本身,如果是引用類型,則存的是對象的指針。

  1. 對齊填充

預設情況下,如果對象頭+對象體大小不是8位元組的倍數,則通過該部分進行補齊,比如對象頭+對象體大小隻有30位元組,則需要補齊到32位元組,這裡的對齊填充就是2位元組。預設情況下,JVM中對象是以8位元組對齊的,若對象頭加上對象體是8的倍數時,則不存在位元組對齊,否則會填充補齊到8的倍數。
對象結構如下圖所示。
image.png
通過圖中可以看出,數組對象只是在對象頭裡多了數組長度這一項,普通對象(非數組對象)沒有這項,也不分配記憶體空間。
對象結構及占用空間大小如下圖所示。
image.png
涉及指針壓縮的地方有兩個,一個是對象頭裡的類型指針,一個是對象體里的引用類型指針,這篇文章里有詳細的介紹:JVM之指針壓縮

對象頭

對象頭包含三部分

  • Markword:存儲對象自身運行時數據如hashcode、gc分代年齡及鎖信息等,64位系統總共占用8個位元組。
  • 類型指針:對象指向類元數據地址的指針,jdk8預設開啟指針壓縮,64位系統占4個位元組
  • 數組長度:若對象不是數組,則沒有該部分,不分配空間大小,若是數組,則為4個位元組長度

對象頭占用空間大小如下表所示。
image.png

Markword

存儲對象自身運行時數據如hashcode、gc分代年齡及鎖信息等,64位系統總共占用8個位元組,也就是64bit,64位的二進位0和1。
image.png
解釋如下:

  • 對象的hashCode占31位,重寫類的hashCode方法返回int類型,只有在無鎖情況下,是在有調用的情況下會計算該值並寫到對象頭中,其他情況該值是空的。
  • 分代年齡占4位,最大值也就是15,在GC中,當survivor區中對象複製一次,年齡加1,預設是到15之後會移動到老年代。
  • 是否偏向鎖占1位,無鎖和偏向鎖的最後兩位都是01,使用這一位來標識區分是無鎖還是偏向鎖。
  • 鎖標誌位占2位,鎖狀態標記位,同是否偏向鎖標誌位標識對象處於什麼鎖狀態。
  • 偏向線程ID占54位,只有偏向鎖狀態才有,這個ID是操作系統層面的線程唯一id,跟java中的線程id是不一致的。

類型指針

類型指針指向類的元數據地址,JVM通過這個指針確定對象是哪個類的實例。32位的JVM占32位,4個位元組,64位的JVM占64位,8個位元組,但是64位的JVM預設會開啟指針壓縮,壓縮後也只占4位元組。
64位虛擬機中在堆記憶體小於32GB的情況下,UseCompressedOops是預設開啟的,該參數表示開啟指針壓縮,會將原來64位的指針壓縮為32位。
-XX:+UseCompressedClassPointers //開啟壓縮類指針
-XX:-UseCompressedClassPointers //關閉壓縮類指針

這個JVM參數依賴UseCompressedOops這個參數,UseCompressedOops開啟,UseCompressedClassPointers預設開啟,可手工關閉,UseCompressedOops關閉,UseCompressedClassPointers不管開啟還是關閉都不生效即不壓縮。

數組長度

如果對象是普通對象非數組對象,則沒有這部分,不占用空間。
如果對象是一個數組,則將數組的長度存到對象頭裡,表示數組的大小。

對象體

對象體里放的是非靜態的屬性,也包括父類的所有非靜態屬性(private修飾的也在這裡,不區分可見性修飾符),基本類型的屬性存放的是具體的值,引用類型及數組類型存放的是引用指針。

對齊填充

虛擬機為了高效定址,採用8位元組對齊,所以對象大小不是8的倍數時,會補齊對應的位置,比如對象頭+對象體是32位元組時,則不需要對齊填充,對象頭+對象體是12位元組時,則需補齊4位。

對象大小的計算

對象的大小跟指針壓縮是否開啟有關,可通過以下兩個參數控制。
UseCompressedClassPointers:壓縮類指針(開啟時類指針占4位元組,關閉時類指針占8位元組)
UseCompressedOops:壓縮普通對象指針(開啟時引用對象指針占4位元組,關閉時引用對象指針占8位元組)
這兩個參數預設是開啟的,即-XX:+UseCompressedClassPointers,-XX:+UseCompressedOops,也可手動設置,如下所示

-XX:+UseCompressedClassPointers //開啟壓縮類指針
-XX:-UseCompressedClassPointers //關閉壓縮類指針
-XX:+UseCompressedOops  //開啟壓縮普通對象指針
-XX:-UseCompressedOops  //關閉壓縮普通對象指針

32位HotSpot VM是不支持UseCompressedOops參數的,只有64位HotSpot VM才支持。
Oracle JDK從6 update 23開始在64位系統上會預設開啟壓縮指針。

以下表格展示了對象中各部分所占空間大小,單位:位元組。

類型 所屬部分 占用空間大小(壓縮開啟) 占用空間大小(壓縮關閉)
Markwork 對象頭 8 8
類型指針 對象頭 4 8
數組長度 對象頭 4 4
byte 對象體 1 1
boolean 對象體 1 1
short 對象體 2 2
char 對象體 2 2
int 對象體 4 4
float 對象體 4 4
long 對象體 8 8
double 對象體 8 8
對象引用指針 對象體 4 8
對齊填充 對齊填充 對象頭+對象體是8的倍數?0 :8 -(對象頭+對象體)% 8 對象頭+對象體是8的倍數?0 :8 -(對象頭+對象體)% 8

對象大小計算公式
對象大小=對象頭 + 對象體(對象是數組時,對象體的大小=引用指針占用空間大小*對象個數) + 對齊填充
64位操作系統32G記憶體以下,預設開啟對象指針壓縮,對象頭是12位元組,關閉指針壓縮,對象頭是16位元組。記憶體超過32G時,則自動關閉指針壓縮,對象頭占16位元組。

對象分析

有了以上的理論知識,我們通過實際案例進行對象分析。
使用 JOL 工具分析 Java 對象大小
maven依賴

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

常用類及方法
查看對象內部信息: ClassLayout.parseInstance(obj).toPrintable()
查看對象外部信息:GraphLayout.parseInstance(obj).toPrintable()
查看對象占用空間總大小:GraphLayout.parseInstance(obj).totalSize()
查看類內部信息:ClassLayout.parseClass(Object.class).toPrintable()

使用到的測試類:

@Setter
class Goods {
    private byte b;
    private char type;
    private short age;
    private int no;
    private float weight;
    private double price;
    private long id;
    private boolean flag;
    private String goodsName;
    private LocalDateTime produceTime;
    private String[] tags;
    public static String str;
    public static int temp;
}

非數組對象,開啟指針壓縮

64位JVM,堆記憶體小於32G的情況下,預設是開啟指針壓縮的。

public static void main(String[] args) {
    Goods goods = new Goods();
    goods.setAge((short) 10);
    goods.setNo(123456);
    goods.setId(111L);
    goods.setGoodsName("速食麵");
    goods.setFlag(true);
    goods.setB((byte)1);
    goods.setPrice(1.5d);
    goods.setProduceTime(LocalDateTime.now());
    goods.setType('A');
    goods.setWeight(0.065f);
    goods.setTags(new String[] {"food", "convenience", "cheap"});
    Goods.str = "test";
    Goods.temp = 222;
    System.out.println(ClassLayout.parseInstance(goods).toPrintable());
}

計算對象大小:
先不看輸出結果,按上面的公式計算一下對象的大小:
對象頭:8位元組(Markword)+4位元組(類指針)=12位元組
對象體:1位元組(屬性b)+ 2位元組(屬性type)+ 2位元組(屬性age)+ 4位元組(屬性no)+ 4位元組(屬性weight)+ 8位元組(屬性price)+ 8位元組(屬性id)+ 1位元組(屬性flag) + 4位元組(屬性goodsName指針) + 4位元組(屬性produceTime指針) + 4位元組(屬性tags指針)= 42位元組(註意:靜態屬性不參與對象大小計算)
對齊填充:8 -(對象頭+對象體)% 8 = 8 - (12 + 42) % 8 = 2位元組
對象大小=對象頭 + 對象體 + 對齊填充 = 12位元組 + 42位元組 + 2位元組 = 56位元組。
執行看運行結果:

com.star95.study.jvm.Goods object internals:
OFF  SZ                      TYPE DESCRIPTION               VALUE
  0   8                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                           (object header: class)    0x2000c043
 12   4                       int Goods.no                  123456
 16   8                    double Goods.price               1.5
 24   8                      long Goods.id                  111
 32   4                     float Goods.weight              0.065
 36   2                      char Goods.type                A
 38   2                     short Goods.age                 10
 40   1                      byte Goods.b                   1
 41   1                   boolean Goods.flag                true
 42   2                           (alignment/padding gap)   
 44   4          java.lang.String Goods.goodsName           (object)
 48   4   java.time.LocalDateTime Goods.produceTime         (object)
 52   4        java.lang.String[] Goods.tags                [(object), (object), (object)]
Instance size: 56 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total

這裡有一個特殊的地方,列印輸出的屬性順序跟代碼里的順序不一致,這是因為JVM進行優化,也就是指令重排序,會根據屬性類型的大小、執行的先後順序對結果是否有影響、最小填充大小等因素計算出對象最小應占用的空間。

非數組對象,關閉指針壓縮

計算對象大小:
關閉壓縮指針,類指針和引用對象指針都占8位元組,推算一下對象大小:
對象頭:8位元組(Markword)+8位元組(類指針)=16位元組
對象體:1位元組(屬性b)+ 2位元組(屬性type)+ 2位元組(屬性age)+ 4位元組(屬性no)+ 4位元組(屬性weight)+ 8位元組(屬性price)+ 8位元組(屬性id)+ 1位元組(屬性flag) + 8位元組(屬性goodsName指針) + 8位元組(屬性produceTime指針) + 8位元組(屬性tags指針)= 54位元組(註意:靜態屬性不參與對象大小計算)
對齊填充:8 -(對象頭+對象體)% 8 = 8 - (16 + 54) % 8 = 2位元組
對象大小=對象頭 + 對象體 + 對齊填充 = 16位元組 + 54位元組 + 2位元組 = 72位元組。
運行時增加JVM參數如下:

-XX:-UseCompressedClassPointers -XX:-UseCompressedOops
public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("速食麵");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        System.out.println(ClassLayout.parseInstance(goods).toPrintable());
    }
}

執行看運行結果:

com.star95.study.jvm.Goods object internals:
OFF  SZ                      TYPE DESCRIPTION               VALUE
  0   8                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8                           (object header: class)    0x00000000175647b8
 16   8                    double Goods.price               1.5
 24   8                      long Goods.id                  111
 32   4                       int Goods.no                  123456
 36   4                     float Goods.weight              0.065
 40   2                      char Goods.type                A
 42   2                     short Goods.age                 10
 44   1                      byte Goods.b                   1
 45   1                   boolean Goods.flag                true
 46   2                           (alignment/padding gap)   
 48   8          java.lang.String Goods.goodsName           (object)
 56   8   java.time.LocalDateTime Goods.produceTime         (object)
 64   8        java.lang.String[] Goods.tags                [(object), (object), (object)]
Instance size: 72 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total

數組對象開啟指針壓縮

計算對象大小:
預設是開啟壓縮指針的,類指針和引用對象指針都占4位元組,推算一下對象大小:
對象頭:8位元組(Markword)+ 4位元組(類指針) + 4位元組(數組長度)= 16位元組
對象體:4位元組 * 3 = 12位元組
對齊填充:8 -(對象頭+對象體)% 8 = 8 - (16位元組 + 12位元組)% 8= 4位元組
對象大小=對象頭 + 對象體 + 對齊填充 = 16位元組 + 12位元組 + 4位元組 = 32位元組。

public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("速食麵");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        Goods[] goodsArr = new Goods[3];
        goodsArr[0] = goods;
        System.out.println(ClassLayout.parseInstance(goodsArr).toPrintable());
    }
}

執行看運行結果:

[Lcom.star95.study.jvm.Goods; object internals:
OFF  SZ                         TYPE DESCRIPTION               VALUE
  0   8                              (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                              (object header: class)    0x2000c18d
 12   4                              (array length)            3
 16  12   com.star95.study.jvm.Goods Goods;.<elements>         N/A
 28   4                              (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

數組對象關閉指針壓縮

計算對象大小:
關閉壓縮指針,類指針和引用對象指針都占8位元組,推算一下對象大小:
對象頭:8位元組(Markword)+8位元組(類指針) + 4位元組(數組長度)=20位元組
對象體:8位元組 * 3 = 24位元組
對齊填充:8 -(對象頭+對象體)% 8 = 8 - (20+ 24) % 8 = 4位元組
對象大小=對象頭 + 對象體 + 對齊填充 = 20位元組 + 24位元組 + 4位元組 = 48位元組。
運行時增加JVM參數如下:

-XX:-UseCompressedClassPointers -XX:-UseCompressedOops
public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("速食麵");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        Goods[] goodsArr = new Goods[3];
        goodsArr[0] = goods;
        System.out.println(ClassLayout.parseInstance(goodsArr).toPrintable());
    }
}

執行看運行結果:

[Lcom.star95.study.jvm.Goods; object internals:
OFF  SZ                         TYPE DESCRIPTION               VALUE
  0   8                              (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8                              (object header: class)    0x0000000017e04d70
 16   4                              (array length)            3
 20   4                              (alignment/padding gap)   
 24  24   com.star95.study.jvm.Goods Goods;.<elements>         N/A
Instance size: 48 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

通過以上對象分析,我們看到在開啟壓縮指針的情況下,對象的大小會小很多,節省了記憶體空間。

總結

通過以上的分析,基本已經把java對象的結構講清楚了,另外對象占用記憶體空間大小也計算出來,有助於進行JVM調優分析,64位的虛擬機記憶體在32G以下時預設是開啟壓縮指針的,超過32G自動關閉壓縮指針,主要目的都是為了提高定址效率。
另外,本文是通過JOL工具計算對象占用空間的大小,不包括引用對象實際占用的記憶體大小,因為計算時是按引用對象的指針占用空間大小計算的,可能跟其他工具計算的結果不一樣,具體跟工具的計算邏輯有關,比如跟JDK自帶的jvisualvm工具通過堆dump出來看到的對象大小不一樣,感興趣的可自行驗證。


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

-Advertisement-
Play Games
更多相關文章
  • ## 教程簡介 大數據分析是指對規模巨大的數據進行分析。大數據可以概括為5個V, 數據量大(Volume)、速度快(Velocity)、類型多(Variety)、價值(Value)、真實性(Veracity)。大數據作為時下最火熱的IT行業的辭彙,隨之而來的數據倉庫、數據安全、數據分析、數據挖掘等等 ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《JavaCV的攝像頭實戰》系列的 ...
  • # 1、什麼是MapReduce 1. Hadoop MapReduce 是一個 `分散式計算框架`,用於輕鬆編寫分散式應用程式,這些應用程式以可靠,容錯的方式並行處理大型硬體集群(數千個節點)上的大量數據(多TB數據集) 2. MapReduce 是一種`面向海量數據`處理的一種指導思想,也是一種 ...
  • # Cookie對象 Cookie是瀏覽器提供的一種技術,通過伺服器的程式能將一些只須保存在客戶端,或者在客戶端進行處理的數據,放在本地的電腦上,不需要通過網路傳輸,因而提高網頁處理的效率,並且能夠減少伺服器的負載,但是由於Cook是伺服器端保存在客戶端的信息,所以其安全性也是很差的。例如常見的記 ...
  • `numpy` 數組通常是用於數值計算的多維數組,而排序功能可以快速、準確地對數據進行排序,從而得到更加清晰、易於分析的結果。 在數據分析和處理過程中,常常需要對數據進行排序,以便更好地理解和發現其中的規律和趨勢。 排序會應用在很多場景中,比如: 1. 數據分類:將數據按照一定的特征進行分類,可以通 ...
  • ## 前言 閱讀此篇前,可先閱讀[尾碼數組](https://www.cnblogs.com/pdpdzaa/p/17436993.html) ## LCP ```LCP``` 就是最長公共首碼,在尾碼數組中,$LCP(i,j)$ 就代表從 $sa_i$ 開始的尾碼和從 $sa_j$ 開始的尾碼的最 ...
  • 我在不要更新挑戰中堅持了一年🎉🎉🎉,你也來試試吧(咕咕咕)! 好言歸正傳,本次更新帶來的是經典游戲掃雷,基於JavaFX實現。篇幅有限,文章主要介紹核心操作實現,不會列出所有代碼。需要完整源碼或是想預覽最終效果,可以點擊下方鏈接。後續會逐步更新細節實現方面的內容,將來吧反正(肯定不鴿!) 視頻 ...
  • ## 背景 隨著所在公司的發展,應用服務的規模不斷擴大,原有的垂直應用架構已無法滿足產品的發展,幾十個工程師在一個項目里並行開發不同的功能,開發效率不斷降低。 於是公司開始全面推進服務化進程,把團隊內的大部分工程師主要精力全部都集中到服務化中。服務化可以讓每個工程師僅在自己負責的子項目中進行開發,提 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...