Java 序列化內部機制完整技術指南
Java 序列化將物件轉為位元組流以便持久化或網路傳輸。本文深入剖析序列化演算法、二進制格式結構、類繼承處理、物件引用共享與 serialVersionUID 關鍵機制。
序列化基礎條件
- 實作
Serializable 標記介面 (無方法)
- 所有非 transient 欄位需可序列化 (基本類型、String、實作 Serializable 的物件)
- 建議顯式宣告
private static final long serialVersionUID
序列化演算法四步驟
- 寫入類中繼資料:類名、serialVersionUID、旗標、欄位描述
- 遞歸寫入父類描述:直到
java.lang.Object
- 從最頂層父類開始寫入實際資料值
- 遞歸寫入子類實例資料
二進制格式實戰解析
範例類別:
class Parent implements Serializable { int parentVersion = 10; }
class Contain implements Serializable { int containVersion = 11; }
class SerialTest extends Parent implements Serializable {
int version = 66;
Contain con = new Contain();
}
序列化輸出 (十六進制關鍵段落):
| 位元組 | 含義 | 說明 |
AC ED | STREAM_MAGIC | 序列化協定魔數 |
00 05 | STREAM_VERSION | 協定版本 |
73 | TC_OBJECT | 新物件開始 |
72 | TC_CLASSDESC | 新類描述開始 |
00 0A | 類名長度 | 10 字元 |
53 65 72 69 61 6C 54 65 73 74 | 類名 | "SerialTest" UTF-8 |
05 52 81 5A AC 66 02 F6 | serialVersionUID | 8 位元組 ID |
02 | 旗標 | SC_SERIALIZABLE (可序列化) |
00 02 | 欄位數 | 2 個欄位 |
49 00 07 version | 欄位 1 | I=int, 長度7, "version" |
4C 00 03 con 74 00 09 Lcontain; | 欄位 2 | L=object, "con", TC_STRING, JVM 簽名 |
78 | TC_ENDBLOCKDATA | 類描述結束 |
| ...父類 Parent 描述... | | |
70 | TC_NULL | 無更多父類 |
00 00 00 42 | version 值 | 66 (int) |
00 00 00 0A | parentVersion 值 | 10 (int) |
73 72 ... | Contain 物件 | 新物件 + 類描述 + 值 11 |
關鍵機制深度解析
serialVersionUID 版本控制
- 未宣告 → JVM 依類結構自動生成 (極不穩定,增減欄位即變)
- 顯式宣告 → 相容性由開發者控制
- 相容變更:增欄位、增方法、改方法實作
- 不相容變更:刪欄位、改欄位類型、改 serialVersionUID、改繼承關係
物件引用共享與循環引用
序列化使用「引用句柄」機制:
- 首次遇到物件 → 分配句柄、寫入完整描述
- 再次遇到同一物件 → 寫入
TC_REFERENCE + 句柄編號
- 完美處理循環引用 (A→B→A) 與共享引用 (A→C, B→C)
特殊欄位處理
| 修飾符 | 序列化行為 |
transient | 跳過不序列化,反序列化為預設值 (null/0/false) |
static | 屬於類非實例,不序列化 |
final | 基本類型/String 序列化;物件引用序列化引用目標 |
自訂序列化:writeObject/readObject
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 執行預設序列化
// 額外邏輯: 加密、壓縮、寫入衍生資料
out.writeObject(computedHash);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 執行預設反序列化
// 額外邏輯: 驗證、解密、重建 transient 欄位
this.hash = (String) in.readObject();
if (!verifyHash()) throw new InvalidObjectException("資料損毀");
}
Externalizable 完全控制
public class MyData implements Externalizable {
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(version);
out.writeUTF(name);
// 完全手動控制
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
version = in.readInt();
name = in.readUTF();
}
}
效能與安全考量
- 效能:序列化較慢、產出較大;高效能場景考慮 Protobuf、Avro、Kryo
- 安全:反序列化任意類風險 (CVE-2015-4852 等);嚴禁反序列化不可信資料、使用
ObjectInputFilter (Java 9+)
- 版本演進:嚴格管理 serialVersionUID、提供遷移邏輯、測試向前/向後相容
理解序列化二進制格式與演算法,是排查 NotSerializableException、InvalidClassException、資料不一致等問題的關鍵。
- 上一篇文章: Java 執行緒中斷機制深度解析:interrupt、InterruptedException 與中斷狀態處理模式
- 下一篇文章: Java 執行緒中斷本質與程式設計準則:協作式機制、JVM 實作與任務框架最佳實務
Copyright ©
程式碼之家 All Rights Reserved