【JDK原始碼分析】01-物件序列化ObjectOutputStream
上一篇文章提到反序列化列舉類仍是單例,而普通類物件不是單例,下面我們先分析一下序列化過程,序列化就是呼叫ObjectOutStream的物件的writeObject方法。我們先看一下序列化涉及的比較重要的一個ObjectStreamClass,JDK中的描述是:類的序列化描述符。它包含類描述資訊,欄位的描述資訊和 serialVersionUID。可以使用 lookup 方法找到/建立在此 Java VM 中載入的具體類的 ObjectStreamClass。ObjectOutputStream在序列化一個物件的時候就把這個物件的類描述資訊序列化的,要想獲得一個可序列化類的描述資訊那我們就可以呼叫ObjectStreamClass的lookup方法:
public static ObjectStreamClass lookup(Class<?> cl) { return lookup(cl, false); }
static ObjectStreamClass lookup(Class<?> cl, boolean all) { if (!(all || Serializable.class.isAssignableFrom(cl))) { return null; } processQueue(Caches.localDescsQueue, Caches.localDescs);//處理已被GC回收的Class物件所對應的ObjectStreamClass物件WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue); Reference<?> ref = Caches.localDescs.get(key);//先嚐試從快取中找到cl對應的ObjectStreamClass物件 Object entry = null; if (ref != null) { entry = ref.get();//如果存在則取出 } EntryFuture future = null; if (entry == null) {//如果不存在則建立一個Future作為後續生成ObjectStreamClass例項的容器 EntryFuture newEntry = newEntryFuture(); Reference<?> newRef = new SoftReference<>(newEntry); do { if (ref != null) {//移除舊的不引用任何物件的Reference Caches.localDescs.remove(key, ref); } ref = Caches.localDescs.putIfAbsent(key, newRef);//新建cl對應的引用,此時引用的只是一個空Future if (ref != null) { entry = ref.get(); } } while (ref != null && entry == null); if (entry == null) { future = newEntry; } } //取得快取中的直接返回 if (entry instanceof ObjectStreamClass) { // check common case first return (ObjectStreamClass) entry; } if (entry instanceof EntryFuture) { future = (EntryFuture) entry; if (future.getOwner() == Thread.currentThread()) { /* * Handle nested call situation described by 4803747: waiting * for future value to be set by a lookup() call further up the * stack will result in deadlock, so calculate and set the * future value here instead. */ entry = null; } else { entry = future.get(); } } if (entry == null) { try { entry = new ObjectStreamClass(cl);//這是重點下面著重分析一下 } catch (Throwable th) { entry = th; } if (future.set(entry)) { Caches.localDescs.put(key, new SoftReference<Object>(entry));//更新Future為ObjectStreamClass } else { // nested lookup call already set future entry = future.get(); } } 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); } }
private ObjectStreamClass(final Class<?> cl) { this.cl = cl; name = cl.getName();//類名 isProxy = Proxy.isProxyClass(cl);//是否是JDK動態代理產生的物件類 isEnum = Enum.class.isAssignableFrom(cl);//是否列舉累 serializable = Serializable.class.isAssignableFrom(cl);//時候實現了序列化介面 externalizable = Externalizable.class.isAssignableFrom(cl);//是否實現了Externalozable介面 Class<?> superCl = cl.getSuperclass(); superDesc = (superCl != null) ? lookup(superCl, false) : null;//記錄父類的描述資訊 localDesc = this;//儲存本例項引用 if (serializable) {//實現了序列化介面的 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { if (isEnum) { suid = Long.valueOf(0);//列舉類固定serialVersionUID為0 fields = NO_FIELDS;//列舉欄位設為空 return null; } if (cl.isArray()) { fields = NO_FIELDS;//陣列.class的類描述欄位設為空 return null; } suid = getDeclaredSUID(cl);//若不是列舉和陣列讀取類的靜態final的長整型欄位serialVersionUID,若沒明確指定該欄位值則通過getSerialVersionUID()方法分配一個值 try { fields = getSerialFields(cl);//獲取類中定義的私有靜態final的ObjectStreamField陣列serialPersistentFields,它指定了需要序列化的欄位,如果沒有指定則預設使用非靜態瞬時的成員欄位 computeFieldOffsets(); } catch (InvalidClassException e) { serializeEx = deserializeEx = new ExceptionInfo(e.classname, e.getMessage()); fields = NO_FIELDS; } if (externalizable) { cons = getExternalizableConstructor(cl); } else { cons = getSerializableConstructor(cl);//獲取最近沒有實現序列化介面的祖先類的無參構造方法要是public或protected或與父類在一個package中,否則返回null writeObjectMethod = getPrivateMethod(cl, "writeObject",//儲存類的private void writeObject(ObjectOutputStream os)方法 new Class<?>[] { ObjectOutputStream.class }, Void.TYPE); readObjectMethod = getPrivateMethod(cl, "readObject",//儲存類的private void readObject(ObjectInputStream is)方法 new Class<?>[] { ObjectInputStream.class }, Void.TYPE); readObjectNoDataMethod = getPrivateMethod( //儲存類的private void readObjectNoData()方法 cl, "readObjectNoData", null, Void.TYPE); hasWriteObjectData = (writeObjectMethod != null); } domains = getProtectionDomains(cons, cl); writeReplaceMethod = getInheritableMethod( //儲存非static abstract Object writeReplace()方法,可繼承父類的但不能是私有 cl, "writeReplace", null, Object.class); readResolveMethod = getInheritableMethod( //儲存非static abstract Object readResolve()方法 cl, "readResolve", null, Object.class); return null; } }); } else { //沒有實現Serializable介面的類serialVersionUID設為0且類描述欄位陣列置為空陣列 suid = Long.valueOf(0); fields = NO_FIELDS; } try {//欄位資訊反射器,包含了可序列化的成員欄位,基本型別成員數量等資訊 fieldRefl = getReflector(fields, this); } catch (InvalidClassException ex) { // field mismatches impossible when matching local fields vs. self throw new InternalError(ex); } if (deserializeEx == null) { if (isEnum) { deserializeEx = new ExceptionInfo(name, "enum type"); } else if (cons == null) {//注意這個地方也很重要,如果沒有cons反序列化的時候會丟擲no valid constructor deserializeEx = new ExceptionInfo(name, "no valid constructor"); } } for (int i = 0; i < fields.length; i++) { if (fields[i].getField() == null) { defaultSerializeEx = new ExceptionInfo( name, "unmatched serializable field(s) declared"); } } initialized = true; }
有一點需要注意的是deserializeEx,反序列化時會判斷不為空的話丟擲他描述的異常資訊。譬如沒有一個合適的午餐建構函式就會拋no valid constructor,所以一般都要提供一個public的午餐構造方法以便序列化,這個下篇文章會再次提及。
rpublic long getSerialVersionUID() { // REMIND: synchronize instead of relying on volatile? if (suid == null) { suid = AccessController.doPrivileged( new PrivilegedAction<Long>() { public Long run() { return computeDefaultSUID(cl); } } ); } return suid.longValue(); }若果類中沒有定義[ANY-ACCESS-MODIFIER static final long serialVersionUID]欄位,則通過以上方法根據Class物件相關資訊分配一個值。
下面是ObjectStreamField定義,一目瞭然。
public class ObjectStreamField implements Comparable<Object> { /** field name */ private final String name; /** canonical JVM signature of field type */ private final String signature; /** field type (Object.class if unknown non-primitive type) */ private final Class<?> type; /** whether or not to (de)serialize field values as unshared */ private final boolean unshared; /** corresponding reflective field object, if any */ private final Field field; /** offset of field value in enclosing field group */ private int offset = 0;
我們看一下ObjectOutpupStream構造方法
public ObjectOutputStream(OutputStream out) throws IOException { verifySubclass(); bout = new BlockDataOutputStream(out); handles = new HandleTable(10, (float) 3.00); subs = new ReplaceTable(10, (float) 3.00); enableOverride = false; writeStreamHeader();//此處首先往輸出流中寫入magic和版本號 bout.setBlockDataMode(true); if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack(); } else { debugInfoStack = null; } }
protected void writeStreamHeader() throws IOException { bout.writeShort(STREAM_MAGIC);// bout.writeShort(STREAM_VERSION); }
在構造方法中首先呼叫writeStreanHeader()方法往輸出流中寫入magic表明這是一個jdk序列化檔案格式以及版本號。
之後我們就可以呼叫writeObject()方法寫入物件了,實際呼叫了writeObject0()方法
public final void writeObject(Object obj) throws IOException { if (enableOverride) {//預設false writeObjectOverride(obj); return; } try { writeObject0(obj, false);//此方法實際呼叫writeObject0()方法 } catch (IOException ex) { if (depth == 0) { writeFatalException(ex); } throw ex; } }然後來到writeObject0()方法
private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; try { // handle previously written and non-replaceable objects int h; 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; } // check for replacement object Object orig = obj; Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // REMIND: skip this check for strings/arrays? Class<?> repCl; desc = ObjectStreamClass.lookup(cl, true);//1⃣️.獲取累的描述資訊 if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; } if (enableReplace) { Object rep = replaceObject(obj); if (rep != obj && rep != null) { cl = rep.getClass(); desc = ObjectStreamClass.lookup(cl, true); } obj = rep; } // if object replaced, run through original checks a second time 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; } } // remaining cases if (obj instanceof String) { writeString((String) obj, unshared);//2⃣️序列化字串 } else if (cl.isArray()) { writeArray(obj, desc, unshared);//3⃣️序列化陣列 } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared);//4⃣️序列化列舉 } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared);//5⃣️序列化實現了Serializable介面的物件 } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }
上面的方法有點長,核心程式碼就是以上新增的五行註釋,
註釋1⃣️獲取序列物件的類資訊ObjectStreamClass,此類上面已經介紹了,核心類之一。註釋2⃣️到註釋5⃣️分別是序列化不同資料型別,我們分析一下序列化列舉writeEnum()和序列化writeOrdinaryObject()。
private void writeEnum(Enum<?> en, ObjectStreamClass desc, boolean unshared) throws IOException { bout.writeByte(TC_ENUM);//第五個位元組代表資料型別,這裡是列舉 ObjectStreamClass sdesc = desc.getSuperDesc();//父類的描述資訊 writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);//首先寫入類的描述資訊,然後寫入父類的描述資訊 handles.assign(unshared ? null : en);//快取一下,不是重點 writeString(en.name(), false);//寫入列舉name屬性 }
上面程式碼比較重要的就是寫入類的描述資訊writeClassDesc()方法和寫入列舉值writeString()方法。
private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException { int handle; if (desc == null) { writeNull(); } else if (!unshared && (handle = handles.lookup(desc)) != -1) { writeHandle(handle); } else if (desc.isProxy()) { writeProxyDesc(desc, unshared); } else { writeNonProxyDesc(desc, unshared);//列舉型別走這 } }
然後呼叫writeNonProxyDesc()方法:
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) throws IOException { bout.writeByte(TC_CLASSDESC);//代表下一個要寫入的是類描述資訊 handles.assign(unshared ? null : desc); if (protocol == PROTOCOL_VERSION_1) { // do not invoke class descriptor write hook with old protocol desc.writeNonProxy(this); } else { writeClassDescriptor(desc);//先寫入本類的描述資訊 } Class<?> cl = desc.forClass(); bout.setBlockDataMode(true); if (cl != null && isCustomSubclass()) { ReflectUtil.checkPackageAccess(cl); } annotateClass(cl); bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA);//一個類描述資訊的結束位writeClassDesc(desc.getSuperDesc(), false);//然後寫入父類的描述資訊 }
protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { desc.writeNonProxy(this); }寫入類的描述資訊就是呼叫ObjectOutputStream的writeNonProxy()方法。
void writeNonProxy(ObjectOutputStream out) throws IOException { out.writeUTF(name);//寫入類名 out.writeLong(getSerialVersionUID());//上面分析過列舉為0 byte flags = 0; if (externalizable) { flags |= ObjectStreamConstants.SC_EXTERNALIZABLE; int protocol = out.getProtocolVersion(); if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) { flags |= ObjectStreamConstants.SC_BLOCK_DATA; } } else if (serializable) { flags |= ObjectStreamConstants.SC_SERIALIZABLE; } if (hasWriteObjectData) { flags |= ObjectStreamConstants.SC_WRITE_METHOD; } if (isEnum) { flags |= ObjectStreamConstants.SC_ENUM; } out.writeByte(flags); //綜合標誌--實現了哪種序列化介面有writeObjectData()方法是列舉型別嗎 out.writeShort(fields.length);//可序列化的欄位數量 for (int i = 0; i < fields.length; i++) {//將此類中的可序列化欄位資訊以此寫入 ObjectStreamField f = fields[i]; out.writeByte(f.getTypeCode()); out.writeUTF(f.getName()); if (!f.isPrimitive()) { out.writeTypeString(f.getTypeString()); } } }
所以綜上寫入一個列舉型別的順序為 magic(short)->version(short)->TC_ENUM(byte)->TC_CLASSDESC(byte)->className(String)->serialVersionUID(long)->flags(byte)->TC_ENDBLOCKDATA(byte)->父類Enum的類描述資訊->TC_STRING或TC_LONGSTRING(byte)->列舉名稱(String)。
寫一個測試類:
public enum Gender {
MAN,WOMAN;
}
private static void testSeriableEnum() throws IOException {
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(Gender.MAN);
System.out.println(baos.toString());
}
結果如下:
以上就是列舉序列化的全部,列舉的序列化還是清晰明瞭的,先列舉本身然後列舉父類最後列舉名稱。
物件的序列化與列舉最大的區別在於欄位的序列化,我們看一下序列物件呼叫的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 { desc.checkSerialize();bout.writeByte(TC_OBJECT); 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(); } } }
與列舉一樣先寫本類的描述資訊,然後一直沿著最先類寫到第一個沒有實現序列化介面的祖先類(不含)描述資訊,不同的是在writeNonProxy()方法中列舉沒有成員欄位可寫因為fields為長度為0的空陣列,而一般類則會依次寫入欄位類的描述資訊,先基本資料型別後物件型別。之後掉用writeSerialData()方法,在這個方法中如果該類沒有實現前面分析ObjectStreamClass物件儲存的writeObjet()方法則會呼叫defaultWriteFields()方法寫入該物件成員變亮的值。
private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException { Class<?> cl = desc.forClass(); if (cl != null && obj != null && !cl.isInstance(obj)) { throw new ClassCastException(); } desc.checkDefaultSerialize(); int primDataSize = desc.getPrimDataSize(); if (primVals == null || primVals.length < primDataSize) { primVals = new byte[primDataSize]; } desc.getPrimFieldValues(obj, primVals); bout.write(primVals, 0, primDataSize, false);//成員變數是基本資料型別直接序列化ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; desc.getObjFieldValues(obj, objVals); for (int i = 0; i < objVals.length; i++) { if (extendedDebugInfo) { debugInfoStack.push( "field (class \"" + desc.getName() + "\", name: \"" + fields[numPrimFields + i].getName() + "\", type: \"" + fields[numPrimFields + i].getType() + "\")"); } try {//成員變數是一般類型別再次呼叫writeObject0()方法序列化此成員變數的物件,一個遞迴過程。 writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } }
序列化物件欄位的過程就是如果該欄位是基本資料型別直接序列化,物件型別將該成員變數引用的物件再次當作一個可序列化物件傳入writeObject0方法的引數中是一個遞迴的過程。綜上,一般類的序列化過程就是首先將該類的描述資訊序列化,然後將其中所有可序列化的成員變數遞迴的方式序列化(成員變數是基本資料型別直接序列化,一般類型別遞迴序列化)。
寫個例子測試一下:
public class Human{
public Human(String a){}
}
public class Person extends Human implements Serializable{
public String xyz="lmn";
public String name;
public int age=55;
public int length=66;
public int width=77;
public Person(String name) {
super(name);
this.name=name;
}
}
private static void testSeriableEnum() throws IOException {
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
// oos.writeObject(Gender.MAN);
oos.writeObject(new Person("abc"));
System.out.println(baos.toString());
}
執行結果:
注意因為Human沒有Serializable介面,所以序列化結果不包含Human的相關資訊。