深入理解Java序列化機制:ObjectOutputStream源碼簡要分析

一、引言

java.io.ObjectOutputStream是實現序列化的關鍵類,它可以將一個對象轉換成二進制流,然後可以通過ObjectInputStream將二進制流還原成對象。

在閱讀ObjectOutputStream源碼之前,我們先來回顧一下序列化相關的基礎知識:
1、需要序列化的類必須實現java.io.Serializable接口,否則會拋出NotSerializableException異常
2、如果檢測到反序列化後的類的serialVersionUID和對象二進制流的serialVersionUID不同,則會拋出
異常。
3、Java的序列化會將一個類包含的引用中所有的成員變量保存下來(深度複製),所以裏面的引用類型必須也要實現java.io.Serializable接口。
4、對於不採用默認序列化或無須序列化的成員變量,可以添加transient關鍵字,並不是說添加了transient關鍵字就一定不能序列化。
5、每個類可以實現readObject、writeObject方法實現自己的序列化策略,即使是transient修飾的成員變量也可以手動調用ObjectOutputStream的writeInt等方法將這個成員變量序列化。

二、使用方法

我們先來回顧一下ObjectOutputStream的使用方法:

class Person implements Serializable {
    private static final long serialVersionUID = 1386583756403881124L;
    String name;
    int age;
}

public class Test {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\out.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Person p = new Person();
        p.name = "LZF";
        p.age = 19;
        oos.writeObject(p);
        oos.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

ObjectOutputStream只有一個public權限的構造方法:該構造方法需要傳入一個OutputStream表示將對象二進制流寫入到指定的OutputStream。
查看out.txt:輸出數據如下:
這裏寫圖片描述

三、源碼簡要分析

ObjectOutputStream的實現很複雜,建議讀者們先對ObjectOutputStream源碼的主要方法先過一遍再往下看。
ObjectOutputStream類定義:

public class ObjectOutputStream extends OutputStream 
        implements ObjectOutput, ObjectStreamConstants {
    //...
}
  • 1
  • 2
  • 3
  • 4

ObjectOutputStream繼承了OutputStream類,實現了ObjectOutput接口和ObjectStreamConstants接口
ObjectStreamConstants接口並沒有定義方法,其內部定義了很多byte類型常量,表示序列化後的單個字節數據的含義。

瞭解完這些成員變量後,我們從幾個最常用的序列化操作爲切入點分析:ObjectOutputStream的構造方法和writeObject方法。
ObjectOutputStream的構造方法:

public ObjectOutputStream(OutputStream out) throws IOException {
    //檢查繼承權限
    verifySubclass();
    //構造一個BlockDataOutputStream用於向out寫入序列化數據
    bout = new BlockDataOutputStream(out);
    //構造一個大小爲10,負載因子爲3的HandleTable和ReplaceTable
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);
    //恆爲false,除非子類調用protected構造方法
    enableOverride = false;
    writeStreamHeader();
    //將緩存模式打開,寫入數據時先寫入緩衝區
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

BlockDataOutputStream是ObjectOutputStream的內部類,它將構造ObjectOutputStream傳入的OutputStream實例包裝起來,當外部類ObjectOutputStream需要向這個OutputStream寫入序列化數據時,就由這個類來完成實際的寫入操作。

構造方法首先調用verifySubclass方法分析現在構造的是不是ObjectOutputStream的子類,即:

private void verifySubclass() {
    Class<?> cl = getClass();
    //如果構造的不是ObjectOutputStream的子類則直接返回
    if (cl == ObjectOutputStream.class)
        return;
    //否則獲取安全管理器檢查是否有繼承ObjectOutputStream的權限
    SecurityManager sm = System.getSecurityManager();
    if (sm == null)
        return;
    //移除Caches中已經失去引用的Class對象
    processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits);
    //將ObjectOutputStream的子類存入Caches
    WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue);

    Boolean result = Caches.subclassAudits.get(key);
    if (result == null) {
        result = Boolean.valueOf(auditSubclass(cl));
        Caches.subclassAudits.putIfAbsent(key, result);
    }
    if (result.booleanValue())
        return;
    //如果沒有權限則拋出SecurityException異常
    sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

該方法如果識別到構造的是ObjectOutputStream的子類,則會檢查是否擁有SUBCLASS_IMPLEMENTATION_PERMISSION權限,否則拋出SecurityException異常。
另外,ObjectOutputStream通過一個Cache靜態內部類中的ConcurrentHashMap來緩存ObjectOutputStream子類信的息。Class類通過內部類WeakClassKey(繼承WeakReference,將一個弱引用指向一個Class對象)存儲。

private static class Caches {
    static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits = new ConcurrentHashMap<>();
    static final ReferenceQueue<Class<?>> subclassAuditsQueue = new ReferenceQueue<>();
}
  • 1
  • 2
  • 3
  • 4

在進行完ObjectOutputStream的類型檢查後,構造方法會隨之構建一個BlockDataOutputStream用於向傳入的OutputStream寫入對象信息,並構建長度爲10,負載因子爲3的HandleTable和ReplaceTable。隨後,將魔數(0xACED)和版本標識符(0x0005)寫入文件頭,用來檢測是不是一個序列化對象。

protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC); //寫入兩個字節:0xAC和0xED
    bout.writeShort(STREAM_VERSION); //寫入兩個字節:0x00和0x05
}
  • 1
  • 2
  • 3
  • 4

最後根據sun.io.serialization.extendedDebugInfo配置信息決定是否啓用調式信息棧。

private static final boolean extendedDebugInfo =
        java.security.AccessController.doPrivileged(
            new sun.security.action.GetBooleanAction(
                "sun.io.serialization.extendedDebugInfo")).booleanValue();
  • 1
  • 2
  • 3
  • 4

如果extendedDebugInfo爲true,則構造方法會構造一個DebugTraceInfoStack,否則置爲null。

構造完ObjectOutputStream對象後,我們一般會隨之調用writeObject(Object)方法將對象寫入

public final void writeObject(Object obj) throws IOException {
    //在ObjectOutputStream中這個變量恆爲false,只有子類爲true
    if (enableOverride) {
        //實現爲空,供子類重寫用
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0)
            writeFatalException(ex);
        throw ex;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

writeObject方法首先會檢查是否是ObjectOutputStream的子類,如果是則調用writeObjectOverride方法,這個方法默認實現爲空,需要子類根據實際業務需求定製序列化方法。
隨後調用writeObject0方法

private void writeObject0(Object obj, boolean unshared) throws IOException {
    //關閉緩衝模式,直接向目標OutputStream寫入數據
    boolean oldMode = bout.setBlockDataMode(false);
    depth++;
    try {
        int h;
        //處理以前寫過的和不可替換的對象
        //如果obj爲null(只有當obj爲null時纔會返回null)
        if ((obj = subs.lookup(obj)) == null) {
            writeNull();
            return;
        } else if (!unshared && (h = handles.lookup(obj)) != -1) {
            writeHandle(h);
            return;
        } else if (obj instanceof Class) {
            writeClass((Class) obj, unshared);
            return;
        } else if (obj instanceof ObjectStreamClass) {
            writeClassDesc((ObjectStreamClass) obj, unshared);
            return;
        }

        Object orig = obj;
        Class<?> cl = obj.getClass();
        //序列化對象對應的Class對象的詳細信息
        ObjectStreamClass desc;
        for (;;) {
            Class<?> repCl;
            //獲取序列化對象對應的Class對象詳細信息,待會會討論ObjectStreamClass
            desc = ObjectStreamClass.lookup(cl, true);
            //直接break,因爲最後(repCl=obj.getClass())==null恆等於true(我也不知道爲什麼這裏要有for循環)
            if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                    break;
                cl = repCl;
        }
        if (enableReplace) {
            //replaceObject用來替換這個對象進行序列化,默認實現爲空,一般用於子類重寫實現序列化的定製
            Object rep = replaceObject(obj);
            //如果對象被替換了
            if (rep != obj && rep != null) {
                cl = rep.getClass();
                //重新查找對應的ObjectStreamClass
                desc = ObjectStreamClass.lookup(cl, true);
            }
            obj = rep;
        }

        //如果對象被替換了(非ObjectOutputStream子類不會發生)
        if (obj != orig) {
            subs.assign(orig, obj);
            if (obj == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }
        }

        //序列化對象類型爲String、數組、枚舉時,調用定製的寫入方法
        if (obj instanceof String) {
            writeString((String) obj, unshared);
        } else if (cl.isArray()) {
            writeArray(obj, desc, unshared);
        } else if (obj instanceof Enum) {
            writeEnum((Enum<?>) obj, desc, unshared);
        //一般對象類型的寫入(當然需要實現序列化接口)
        } else if (obj instanceof Serializable) {
            writeOrdinaryObject(obj, desc, unshared);
        //如果沒有實現序列化接口會拋出異常
        } else {
            if (extendedDebugInfo)
                throw new NotSerializableException(cl.getName() + "\n" + debugInfoStack.toString());
            else
                throw new NotSerializableException(cl.getName());
        }
    } finally {
        //結束方法前將方法棧深減去1
        depth--;
        bout.setBlockDataMode(oldMode);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

ObjectStreamClass存儲了一個Class對象的信息,其實例變量包括:Class對象,Class名稱,serialVersionUID,實現了Serializable接口還是 Externalizable接口,非transient修飾的變量,自定義的writeObject和readObject的Method對象。

下面來看ObjectStreamClass的lookup方法:

static ObjectStreamClass lookup(Class<?> cl, boolean all) {
    //如果all爲false且cl並沒有實現Serializable接口則直接返回null
    if (!(all || Serializable.class.isAssignableFrom(cl))) {
        return null;
    }
    //清除失去Class引用的ObjectStreamClass緩存
    //(緩存的用途是避免反覆對同一個Class創建ObjectStreamClass對象)
    processQueue(Caches.localDescsQueue, Caches.localDescs);
    //創建一個臨時的WeakClassKey用於從緩存中查找對應的ObjectStreamClass或EntryFuture
    WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
    //獲取保存有ObjectStreamClass或EntryFuture的引用
    Reference<?> ref = Caches.localDescs.get(key);
    Object entry = null;
    //如果引用不爲null則直接獲取其中的對象給entry
    if (ref != null) {
        entry = ref.get();
    }
    EntryFuture future = null;
    //如果引用的對象被GC
    if (entry == null) {
        //創建一個EntryFuture對象並將軟引用newRef指向它
        EntryFuture newEntry = new EntryFuture();
        Reference<?> newRef = new SoftReference<>(newEntry);
        do {
            //從緩存中刪除這個失去引用的鍵值對
            if (ref != null)
                Caches.localDescs.remove(key, ref);
            //將被newRef引用的EntryFuture添加到緩存(這裏使用putIfAbsent而不是put可能是爲了防止有其它線程已經添加了)
            ref = Caches.localDescs.putIfAbsent(key, newRef);
            if (ref != null)
                entry = ref.get();
        //循環直到ref爲null或entry不爲null
        } while (ref != null && entry == null);
        //如果entry爲null
        if (entry == null)
            future = newEntry;
    }
    //如果從緩存中拿到了ObjectStreamClass
    if (entry instanceof ObjectStreamClass) {
        return (ObjectStreamClass) entry;
    }
    //如果從緩存中得到了EntryFuture
    if (entry instanceof EntryFuture) {
        future = (EntryFuture) entry;
        //如果創建這個EntryFuture的線程就是當前線程,即這個EntryFuture
        //是在前面的代碼ref = Caches.localDescs.putIfAbsent(key, newRef);語句中設置的
        if (future.getOwner() == Thread.currentThread()) {
            entry = null;
        } else {
            entry = future.get();
        }
    }
    //如果entry爲null那麼就創建一個新的ObjectStreamClass對象並加入緩存
    if (entry == null) {
        try {
            entry = new ObjectStreamClass(cl);
        } catch (Throwable th) {
            entry = th;
        }
        //設置這個ObjectStreamClass實例
        if (future.set(entry)) {
            Caches.localDescs.put(key, new SoftReference<Object>(entry));
        } else {
            entry = future.get();
        }
    }
    //最後如果entry爲ObjectOutputStream則直接返回,否則拋出異常
    if (entry instanceof ObjectStreamClass) {
        return (ObjectStreamClass) entry;
    } else if (entry instanceof RuntimeException) {
        throw (RuntimeException) entry;
    } else if (entry instanceof Error) {
        throw (Error) entry;
    } else {
        throw new InternalError("unexpected entry: " + entry);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

在ObjectStreamClass類的內部類Caches中,存在一個類型爲ConcurrentMap的靜態成員變量localDescs:

static final ConcurrentMap<WeakClassKey,Reference<?>> localDescs = new ConcurrentHashMap<>();
private static final ReferenceQueue<Class<?>> localDescsQueue = new ReferenceQueue<>();
  • 1
  • 2

ObjectStreamClass引入這個緩存主要是爲了提高獲取類信息的速度,如果反覆對一個類的實例們進行序列化操作,那麼只需要實例化一個ObjectStreamClass實例並導入這個緩存。
WeakClassKey繼承WeakReference,將一個弱引用指向這個Class對象,當對應的ClassLoader失去引用時,不至於導致垃圾回收器無法回收這個Class對象。
引用隊列localDescsQueue主要用於processQueue方法清除localDescs中無用的緩存。

至於ObjectStreamClass的內部類EntryFuture的作用,我個人認爲是爲了實現多線程調用lookup方法而設立的。

private static class EntryFuture {
    private static final Object unset = new Object();
    private final Thread owner = Thread.currentThread();
    private Object entry = unset;

    //entry是ObjectStreamClass實例
    synchronized boolean set(Object entry) {
        if (this.entry != unset)
                return false;
        this.entry = entry;
        //entry已被設置,喚醒正在調用get方法的線程
        notifyAll();
        return true;
    }

    synchronized Object get() {
        boolean interrupted = false;
        while (entry == unset) {
            try {
                //等待到entry被set爲止
                wait();
            } catch (InterruptedException ex) {
                interrupted = true;
            }
        }
        //如果被強制打斷則返回null
        if (interrupted) {
            AccessController.doPrivileged(
                new PrivilegedAction<Void>() {
                    public Void run() {
                        Thread.currentThread().interrupt();
                        return null;
                    }
                });
        }
        //如果是正常被set方法喚醒的則直接返回設置好的ObjectStreamClass
        return entry;
    }
    //返回創建這個EntryFuture的線程
    Thread getOwner() {
        return owner;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

現在回到writeObject0方法中
在獲取到ObjectStreamClass對象後,會判斷需要序列化的類是哪種類型。
下面我們就只分析writeOrdinaryObject方法:

private void writeOrdinaryObject(Object obj, ObjectStreamClass desc,
         boolean unshared) throws IOException {
    if (extendedDebugInfo)
        debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\", " + obj.toString() + ")");
    try {
        //檢查ObjectStreamClass對象
        desc.checkSerialize();
        //寫入字節0x73
        bout.writeByte(TC_OBJECT);
        //寫入對應的Class對象的信息
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
            //寫入這個對象變量信息及其父類的成員變量
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

writeOrdinaryObject最終會以一種遞歸的形式寫入對象信息。
writeSerialData方法會將這個實例及其父類基本數據類型寫入文件,如果檢測到有引用類型,那麼會繼續調用writeObject0方法寫入,直到將這個對象包含的所有信息全部序列化爲止。

暫時就只能分析到這裏了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章