0%

Java序列化流程梳理

序言

浮云游子意,落日故人情。

梳理Java序列化流程。

参考panda师傅。

写在前面

Java序列化就是将对象写入到I/O流之中,通常输出格式为.ser文件。

简单说首先创建一个ObjectOutputStream输出流对象,然后调用ObjectOutputStream对象的writeObject方法,按照规范格式输出可序列化对象。

具体流程

接下来一步一步走,消化一遍panda师傅的文章。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Serialize {
public static class Demo implements Serializable {
private String string;
transient String name = "hello";
public Demo(String s) {
this.string = s;
}
public static void main(String[] args) throws IOException {
Demo demo = new Demo("panda");
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("panda.out"));
outputStream.writeObject(new Demo("panda"));
outputStream.close();
}
}
}

构造函数

1
2
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("panda.out"));
outputStream.writeObject(new Demo("panda"));

首先来到public ObjectOutputStream(OutputStream out)构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 创建写入指定输出流的ObjectOutputStream。
* 此构造函数将序列化流头写入底层流;
* 调用者可能希望立即刷新流,以确保接收ObjectInputStreams的构造函数在读取头时不会阻塞。
* 如果安装了安全管理器,则当重写ObjectOutputStream.putFields或ObjectOutputStream.writeUnshared方法的子类的构造函数直接或间接调用时,此构造函数将检查“enableSublassimplementation”SerializablePermission。
*/
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();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}

一头雾水,接下来一步一步梳理:

verifySubclass()方法:

验证本类(或其子类)实例可以在不违反安全约束的情况下被构造出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}

接下来可以看到对bout,handles,subs,enableOverride一些成员变量进行了复制,跳到他们的声明处看一看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** filter stream for handling block data conversion */
private final BlockDataOutputStream bout;
/** obj -> wire handle map */
private final HandleTable handles;
/** obj -> replacement obj map */
private final ReplaceTable subs;
/** stream protocol version */
private int protocol = PROTOCOL_VERSION_2;
/** recursion depth */
private int depth;
/** buffer for writing primitive field values */
private byte[] primVals;

/** if true, invoke writeObjectOverride() instead of writeObject() */
private final boolean enableOverride;
/** if true, invoke replaceObject() */
private boolean enableReplace;

重点挑这几个说:

  • bout:用来处理数据块转换的数据流,理解为一个容器
  • handles :对象->handle引用
  • subs: 对象->替换对象
  • enableOverride:布尔值 用来决定在序列化Java对象时选用writeObjectOverride方法还是writeObject方法 通常为false

关于 handles 的作用,举个例子,我们知道 Java 序列化除了保存字段信息外,还保存有类信息,当同一个对象序列化两次时第二次只用保存第一次的编号,这样可以大大减少序列化文件的大小。

你肯定对第一个bout的理解有些别扭。

开启支线任务,什么是BlockDataOutputStream

BlockDataOutputStream

BlockDataOutputStreamObjectOutputStream的一个重要内部类,这个类负责将缓冲区中的数据写入到字节流

该类部分内容如下:

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
/**
* Buffered output stream with two modes: in default mode, outputs data in
* same format as DataOutputStream; in "block data" mode, outputs data
* bracketed by block data markers (see object serialization specification
* for details).
*/
private static class BlockDataOutputStream extends OutputStream implements DataOutput
{
/** maximum data block length */
private static final int MAX_BLOCK_SIZE = 1024;
/** maximum data block header length */
private static final int MAX_HEADER_SIZE = 5;
/** (tunable) length of char buffer (for writing strings) */
private static final int CHAR_BUF_SIZE = 256;

/** buffer for writing general/block data */
private final byte[] buf = new byte[MAX_BLOCK_SIZE];
/** buffer for writing block data headers */
private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
/** char buffer for fast string writes */
private final char[] cbuf = new char[CHAR_BUF_SIZE];

/** block data mode */
private boolean blkmode = false;
/** current offset into buf */
private int pos = 0;

/** underlying output stream */
private final OutputStream out;
/** loopback stream (for data writes that span data blocks) */
private final DataOutputStream dout;

/**
* Creates new BlockDataOutputStream on top of given underlying stream.
* Block data mode is turned off by default.
*/
BlockDataOutputStream(OutputStream out) {
this.out = out;
dout = new DataOutputStream(this);
}
...

}

大致意思就是:

缓冲输出流有两种模式:

在默认模式下,以与DataOutputStream相同的格式输出数据;

在 “块数据 “模式下,输出数据 在 “块数据 “模式下,输出由块数据标记括起来的数据 – 详见对象序列化规范。

可以理解成BlockDataOutputStream类是封装后的DataOutputStream类,并且提供了一些缓冲区及成员属性。

在给这些成员变量赋值结束之后,接下来进入writeStreamHeader方法。

image-20210715201920550

熟悉的aced0005出现了,可以理解为bout就是我们的句柄,负责缓存我们的序列化数据。

接下来是bout.setBlockDataMode(true);bout设置为块模式

核心:writeObject

构造函数执行结束之后,就要来到第二句话:outputStream.writeObject(new Demo("panda"));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}

可以说;writeObject 将所有序列委托给了 writeObject0 完成,如果序列化出现异常调用 writeFatalException 方法。

首先是if(enableOverride) ,这里面的enableOverride其实一般都是false(上一步的构造函数),那么就直接进入到writeObject0方法:

核中核 :writeObject0

writeObject0 比较复杂,大致可分为三个部分:

  • 一是判断需不需要序列化;
  • 二是判断是否替换了对象;
  • 三是终于可以序列化。
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
/**
* Underlying writeObject/writeUnshared implementation.
*/
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);
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);
} 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 {
depth--;
bout.setBlockDataMode(oldMode);
}
}

首先第一步就把bout的块模式关掉了,原始模式赋值给了oldMode。

1
boolean oldMode = bout.setBlockDataMode(false);

下一句depth++:表示的是对象序列化的深度。

比如说对象A进行了序列化,那么depth++;

此时如果A中的字段(field)也是一个对象,需要对这个对象再次进行序列化,此时再一次depth++;

细心的可以发现其实在最后的finally里面配套的有depth–;

因而如果不出异常则 depth 最终会是 0,有异常则在 catch 模块时 depth 不为 0。

接下来按照三步走的顺序来解析writeObject0做了什么:

第一步:处理已经处理过的和不可替换的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int h;
// 1. 替换后的对象为 null
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
// 2. handles存储的是已经序列化的对象句柄,如果找到了,直接写一个句柄就可以了
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
// 3. Class 对象
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
// 4. ObjectStreamClass 序列化类的描述信息
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}

已经处理过的不可替换的对象,这些都是不能够序列化的,其实在大多数情况下,我们的代码都不会进入第一步代码块。

首先进入if ((obj = subs.lookup(obj)) == null)这句:

image-20210715203720012

lookup方法会在subs这个map中当前对象obj是否有可替换(writeReplace)对象,如果没有的话,则返回obj对象本身。

也就是说,这个方法实际上就是处理以前写入的对象和不可替换的对象。更直白点的意思,这段代码实际上做的是一个检测功能,如果检测到当前传入对象在 替换哈希表(ReplaceTable) 中无法找到,那么就调用writeNull方法。

下一个if判断是判断当前写入方式是不是“unshared”方式,然后可以看到紧跟着的就是handles.lookup(obj),跟进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Looks up and returns handle associated with given object, or -1 if
* no mapping found.
*/
int lookup(Object obj) {
if (size == 0) {
return -1;
}
int index = hash(obj) % spine.length;
for (int i = spine[index]; i >= 0; i = next[i]) {
if (objs[i] == obj) {
return i;
}
}
return -1;
}

该方法会查找并返回与给定对象关联的handler,如果没有找到映射,则返回 -1;

直白的意思就是说判断是否在“引用哈希表(HandleTable)”中找到该引用,如果有,那么调用writeHandle方法并且返回;如果没找到,那么返回-1,需要进一步序列化处理。

接下来判断当前传入对象是不是特殊类型的ClassObjectStreamClass,如果是,则调用writeClasswriteClassDesc方法并且返回;

总结1: Java 序列化保存了很多与数据无关的数据,如类信息。但 Java 本身也做了一些优化,如 handles 保存了类的句柄,这样重复的类就只用保存一个句柄就可以了。

第二步:查找可替换对象是否已经序列化了

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
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
// 1. 如果要序列化的对象中有 writeReplace 方法,则递归检查最终要输出的对象
for (;;) {
Class<?> repCl;
desc = ObjectStreamClass.lookup(cl, true);
// 如果要序列化的对象中有 writeReplace 方法,则递归检查最终要输出的对象
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl) {
break;
}
cl = repCl;
}
// 2. 子类重写 ObjectOutputStream#replaceObject 方法
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}

// 3. 既然要序列化的对象已经被替换了,此时就需要再次做判断,和步骤1类似
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;
}
}

可以看到是一个for无条件循环,重点是desc = ObjectStreamClass.lookup(cl, true);

这个方法很长,概括一下就是:

ObjectStreamClass.lookup()封装待序列化的类生成类描述符 (返回ObjectStreamClass类型) ,获取包括类名、自定义serialVersionUID、可序列化字段 (返回ObjectStreamField类型) 和构造方法,以及writeObjectreadObject方法等

desc更像是一个类信息模板,需要查找类信息的时候,desc充当句柄。

一步步看,一开始检查是否开启了enableReplace标志位,通常为false,不会进来。

1
2
3
4
5
6
7
8
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}

再往下,如果对象是被替换的,则第二次进行原始检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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;
}
}

如果对象被替换,这里会对原始对象进行二次检查,和最开始的那段代码很像,这里先将替换对象插入到subs(替换哈希表)中,然后进行类似的判断。

第三步:序列化对象

以上执行都完成过后,会处理剩余对象类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// remaining cases
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());
}
}

Switch-case模式:

  • 如果对象是String类型,那么调用writeString方法将数据写入字节流;
  • 如果对象是Array类型,那么调用writeArray方法将数据写入字节流;
  • 如果对象为Enum类型,调用writeEnum方法将数据写入字节流;
  • 如果对象实现了Serializable接口,调用writeOrdinaryObject方法将数据写入字节流;

以上条件都不满足时则抛出NotSerializableException异常信息;

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
// String 类型
private void writeString(String str, boolean unshared) throws IOException {
handles.assign(unshared ? null : str);
long utflen = bout.getUTFLength(str);
if (utflen <= 0xFFFF) { // 长度小于 0xFFFF(65506)
bout.writeByte(TC_STRING); // 类型
bout.writeUTF(str, utflen); // 内容
} else { // 长度大于 0xFFFF(65506)
bout.writeByte(TC_LONGSTRING);
bout.writeLongUTF(str, utflen);
}
}

// Enum 类型
private void writeEnum(Enum<?> en, ObjectStreamClass desc,
boolean unshared) throws IOException {
bout.writeByte(TC_ENUM); // 1. 类型
ObjectStreamClass sdesc = desc.getSuperDesc(); // 2. 类信息
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
handles.assign(unshared ? null : en);
writeString(en.name(), false); // 3. 枚举类的名称
}

// 实现了 Serializable 接口的序列化
private void writeOrdinaryObject(Object obj, ObjectStreamClass desc,
boolean unshared) throws IOException {
desc.checkSerialize();

bout.writeByte(TC_OBJECT); // 1. 类型
writeClassDesc(desc, false); // 2. 类信息
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj); // 3.1 实现 Externalizable 接口的类
} else {
writeSerialData(obj, desc); // 3.2 实现 Serializable 接口的类,数据序列化
}
}

前面三个大同小异,panda师傅简单的举例了一下writeString方法:

1
2
3
4
5
6
7
8
9
10
11
private void writeString(String str, boolean unshared) throws IOException {
handles.assign(unshared ? null : str);
long utflen = bout.getUTFLength(str);
if (utflen <= 0xFFFF) {
bout.writeByte(TC_STRING);
bout.writeUTF(str, utflen);
} else {
bout.writeByte(TC_LONGSTRING);
bout.writeLongUTF(str, utflen);
}
}

首先在写入String对象之前,代码会判断当前写入方式是否是unshared,如果不是unshared方式还需要在handles这个对象映射表中插入当前String对象;接着,代码会调用getUTFLength函数获取String字符串的长度和0xFFFF比较,如果大于该值时,表示当前String对象是一个长字符串对象,那么会先写入TC_LONGSTRING标记(表示是LONGSTRING类型数据),然后写入字符串的长度和内容;如果小于等于该值时,表示当前String对象就是一个普通的字符串对象,那么会先写入TC_STRING标记(表示是一个STRING类型对象),然后写入字符串的长度和内容。

writeOrdinaryObject

终于到了重点分析的方法:

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
/**
* Writes representation of a "ordinary" (i.e., not a String, Class,
* ObjectStreamClass, array, or enum constant) serializable object to the
* stream.
*/
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();
}
}
}

首先来到desc.checkSerialize();,desc其实就是类描述信息,判断当前对象是否是可以被序列化的,也就是是否实现了Serializable接口。

如果是一个可序列化对象,那么会开始写入TC_OBJECT标记(表示开始序列化操作),随后调用writeClassDesc方法写入当前对象所属类的类描述信息,跟进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Writes representation of given class descriptor to stream.
*/
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);// 非代理类信息序列化
}
}

writeClassDesc方法主要用于判断当前的类描述符使用什么方式写入:

  • 如果传入的类描述信息是一个null,那么会调用writeNull方法;
  • 如果没有使用unshared方式,并且可以在handles对象池中找到传入的对象信息,说明类信息已经序列化,那么调用writeHandle保存句柄即可;
  • 如果传入的类是一个动态代理类,那么调用writeProxyDesc方法;
  • 如果上面三个条件都不满足,那么调用writeNonProxyDesc方法。

跟进writeNonProxyDesc(desc, unshared)这里:

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
/**
* Writes class descriptor representing a standard (i.e., not a dynamic
* proxy) class to stream.
*/
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);// 写入类描述符用来保存类信息,本质上也是调用 desc.writeNonProxy(this)
}

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); //递归调用
}

首先写入TC_CLASSDESC标记(表新类描述信息的开始)信息,然后判断使用的模式是unshared模式,那么将desc所表示的类元数据信息插入到handles对象的映射表中,然后根据使用的流协议版本调用不同的write方法,如果使用的流协议是PROTOCOL_VERSION_1,那么直接调用desc成员的writeNonProxy方法,并且将当前引用this作为实参传入到writeNonProxy方法中,如果使用的不是PROTOCOL_VERSION_1协议,那么会调用当前类中的writeClassDescriptor方法。

1
2
3
4
protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException
{
desc.writeNonProxy(this);
}

继续跟进writeNonProxy中:

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
/**
* Writes non-proxy class descriptor information to given output stream.
*/
void writeNonProxy(ObjectOutputStream out) throws IOException {
out.writeUTF(name);
out.writeLong(getSerialVersionUID());

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);

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());
}
}
}

先调用writeUTF方法写入类名到字节流(bout),这里的类名是类全名,带了包名的那种(out.writeUTF(name);

再调用writeLong方法写入serialVersionUID的值到字节流(out.writeLong(getSerialVersionUID());

然后开始写入当前类中成员属性的数量信息到字节流(out.writeShort(fields.length);

最后会写入每一个字段的信息,这里的字段信息包含三部分内容:TypeCodefieldNamefieldType

1
2
3
4
5
6
7
8
9
10
...
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());
}
}

到这里writeClassDescriptor就走完了,回到上一层,发现又打开了块模式bout.setBlockDataMode(true);

再往下会调用annotateClass(cl); 但是跟进去发现什么都没有(迷

在调用annotateClass方法完成过后,代码会关闭Data Block模式,然后写入TC_ENDBLOCKDATA标记(表示当前非动态代理类的描述信息的终止)。

到这里,writeNonProxywriteClassDescriptor流程结束,同样,也导致writeClassDesc流程结束,并且回到writeOrdinaryObject方法。

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
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();
}
}
}

接下来来到handles.assign(unshared ? null : obj); 这里如果使用的模式是unshared模式,则将desc所表示的类元数据信息插入到handles对象的映射表中,最后会判断当前Java对象的序列化语义,如果当前对象不是一个动态代理类并且是实现了外部化的,则调用writeExternalData方法写入对象信息,如果当前对象是一个实现了Serializable接口的,则调用writeSerialData方法写入对象信息。

接下来将类数据信息序列化,写入bout,进入writeSerialData函数

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
/**
* Writes instance data for each serializable class of given object, from
* superclass to subclass.
*/
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
//如果当前对象有自定义writeObject方法
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;

if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);// 调用自定义序列化 writeObject 方法
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}

curPut = oldPut;
} else {//默认序列化操作
defaultWriteFields(obj, slotDesc);
}
}
}

就像注释说的一样,会为给定对象的每个可序列化的类写入实例数据,从父类到子类。

再这个方法内会首先判断当前使用的字节流协议,如果使用的是PROTOCOL_VERSION_1协议,那么回直接调用可序列化对象中的writeExternal方法,如果使用的不是PROTOCOL_VERSION_1协议,那么会先开启Data Block模式,再调用writeExternal方法,调用完毕后再关闭Data Block模式并在该流的最后追加TC_ENDBLOCKDATA标记。

值得一提的是,这个方法有一个切换上下文环境的过程——在检测协议前,首先令curPutcurContext为空,检测并写入数据后,再分别令curContext curPutoldContextoldPut,恢复执行之前的环境。

为什么要切换上下文?

再来看看writeSerialData就明白了

这个方法主要向obj对象写入数据信息,比如字段值和相关引用等,写入的时候会从顶级父类从上至下递归执行

详细过程:

首先ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();

跟进去看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Returns array of ClassDataSlot instances representing the data layout
* (including superclass data) for serialized objects described by this
* class descriptor. ClassDataSlots are ordered by inheritance with those
* containing "higher" superclasses appearing first. The final
* ClassDataSlot contains a reference to this descriptor.
*/
ClassDataSlot[] getClassDataLayout() throws InvalidClassException {
// REMIND: synchronize instead of relying on volatile?
if (dataLayout == null) {
dataLayout = getClassDataLayout0();
}
return dataLayout;
}

翻译注释,该方法返回代表该类描述符所描述的序列化对象的数据布局(包括父类数据)的ClassDataSlot实例阵列。

ClassDataSlots按继承顺序排列,那些包含 “更高 “的父类的实例出现在前面。 最后的ClassDataSlot包含对这个描述符的引用。

也就是说,如果该对象拥有父类,slots里按顺序存放的先是父类后是子类。

也就是说,slots里面存放的是继承结构,用来后续遍历。

接下来开始对slots遍历:

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
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;

if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}

curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}

首先,判断可序列化对象是否重写了writeObject方法,如果重写了该方法,则先开启Data Block模式,去调用writeObject方法,调用结束后再关闭Data Block模式,并且在最后追加TC_ENDBLOCKDATA标记(表示数据块写入终止),如果没有重写该方法,则调用defaultWriteFields方法写入当前对象中的所有字段的值,跟进defaultWriteFields方法:

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
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();//判断该对象是否可序列化

// 1. Java 原生类型 序列化
int primDataSize = desc.getPrimDataSize(); // 1.1 获取原生类型字段的长度
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals); // 1.2 获取原生类型字段的值
bout.write(primVals, 0, primDataSize, false); // 1.3 原生类型序列化

// 2. Java 对象类型 序列化,递归调用 writeObject0 方法
ObjectStreamField[] fields = desc.getFields(false); // 2.1 获取所有序列化的字段
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals); // 2.2 获取所有序列化字段的值
for (int i = 0; i < objVals.length; i++) { // 2.3 递归完成序列化
writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());
}
}

翻译注释:

抓取并写入给定对象的可序列化字段的值到流。 给定的类描述符指定要写哪些字段值,以及它们应该以何种顺序被写入。

也就是说,defaultWriteFields方法负责读取 obj 对象中的字段数据,并且将字段数据写入到字节流中。

首先,desc.checkDefaultSerialize();用来判断该类对象是否是一个可序列化的类。

检查完毕后,分两步:

  1. 基础类型

获取该对象中所有基础类型字段的值:

1
desc.getPrimFieldValues(obj, primVals);

跟进去:

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
/**
* Fetches the serializable primitive field values of object obj and
* marshals them into byte array buf starting at offset 0. The caller
* is responsible for ensuring that obj is of the proper type.
*/
void getPrimFieldValues(Object obj, byte[] buf) {
if (obj == null) {
throw new NullPointerException();
}
/* assuming checkDefaultSerialize() has been called on the class
* descriptor this FieldReflector was obtained from, no field keys
* in array should be equal to Unsafe.INVALID_FIELD_OFFSET.
*/
for (int i = 0; i < numPrimFields; i++) {
long key = readKeys[i];
int off = offsets[i];
switch (typeCodes[i]) {
case 'Z':
Bits.putBoolean(buf, off, unsafe.getBoolean(obj, key));
break;

case 'B':
buf[off] = unsafe.getByte(obj, key);
break;

case 'C':
Bits.putChar(buf, off, unsafe.getChar(obj, key));
break;

case 'S':
Bits.putShort(buf, off, unsafe.getShort(obj, key));
break;

case 'I':
Bits.putInt(buf, off, unsafe.getInt(obj, key));
break;

case 'F':
Bits.putFloat(buf, off, unsafe.getFloat(obj, key));
break;

case 'J':
Bits.putLong(buf, off, unsafe.getLong(obj, key));
break;

case 'D':
Bits.putDouble(buf, off, unsafe.getDouble(obj, key));
break;

default:
throw new InternalError();
}
}
}

这里面的8个case分别对应8个基本类型首字母:int-long-float-double-short-char-byte-boolean

获得这些基础类型字段的值后,bout会将他们写入到字节流。

  1. 对象类型

到这里说明该field是这部分总体来说就是三步:

  • 获取所有序列化的字段
  • 根据desc,获取所有序列化字段的值
  • 递归完成序列化

总结: defaultWriteFields 原生类型直接序列化,而非原生类型则需要递归调用 writeObject0 来对字段序列化。

到这里,整个序列化流程就结束了。

两个特殊点

transient修饰的成员属性具有”不会序列化“的语义,序列化的时候会忽略;

static修饰的成员属性隶属于类而非对象,所以它在序列化的时候同样会被忽略。

补充知识:ObjectStreamClass & ObjectStreamField

我们在刚才分析序列化流程中:

出现了desc这个类描述符。它是属于ObjectStreamClass的类对象。

并且很多次出现了解析Field字段时候,出现了ObjectStreamField这个类。

翻阅资料:

  • ObjectStreamField 按官方的说法是是字段的序列化描述符,本质是对 Field 字段的包装,包括字段名、字段值等。可以通过 ObjectStreamClass#getFields 获取所有需要序列化的字段信息。
  • ObjectStreamClass 按官方的说法是类的序列化描述符本质是对 Class 类的包装,提取了序列化时类的一些信息,包括字段的描述信息和 serialVersionUID。可以使用 lookup 方法找到/创建在此 Java VM 中加载的具体类的 ObjectStreamClass

这里其实我的理解是desc就是类的模板,

ObjectStreamField

依据难度先梳理一下ObjectStreamField这个类。

ObjectStreamField类的实例描述了序列化的对象中成员属性的元数据信息,上边的这个方法用于判断当前描述的成员属性是一个基础类型的数据还是一个对象类型的数据,若当前描述的成员属性是基础类型这个函数返回true,反之返回false。该成员函数判断数据类型的方式是使用的签名中的类型代码来判断,前文多次提到类型代码的概念,目前可以知道对象类型的数据只有两种类型代码——数组array【[】和对象object【L】。

成员属性

1
2
3
4
5
6
7
8
9
10
11
12
/** 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; //属性是否是unshared
/** corresponding reflective field object, if any */
private final Field field; //当前成员属性的JVM级别的元数据信息,在序列化提取成员属性的元数据信息时,会使用J反射,该成员的类型是java.lang.reflect.Field;
/** offset of field value in enclosing field group */
private int offset = 0; //偏移量,一个对象的成员属性个数超过一个时,JVM会将会把所有的成员属性打包成一个“组”来操作,而offset就是这个组中当前描述的成员属性的偏移量,上层的ObjectStreamClass在调用当前这个成员属性的时候就使用偏移量进行引用定位操作;

这里梳理一下:类型,类型代码,类型签名

  • 类型:类对象的型号,Java的成员属性的类型一般对应的Java数据类型为Class<?>;
  • 类型代码:类型代码的数据也是用于JVM判断成员属性数据类型的一种方式,但类型代码的Java数据类型是char,比如‘L’,它一般通过一个字符\判断**当前的Java数据类型,序列化时它会把这个字符转换成二进制数据;
  • 类型签名:类型签名的Java数据类型是一个String类型,比如:‘Ljava/lang/String;’,它和类型代码一样可以用于JVM判断成员属性的数据类型,但是不仅仅如此,JVM在处理类型签名的时候,针对成员属性、成员函数、类本身都可以使用统一的方式来区分,在JVM里面类型签名相当于类型的唯一标识,它的使用 范围比类型代码更加广阔;

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
public ObjectStreamField(String name, Class<?> type) {
this(name, type, false);
}
public ObjectStreamField(String name, Class<?> type, boolean unshared) {
if (name == null) {
throw new NullPointerException();
}
this.name = name;
this.type = type;
this.unshared = unshared;
signature = getClassSignature(type).intern();
field = null;
}

可以看到这里第一个构造函数调用了第二个构造函数,并且给unshared属性赋值为false。

注意,这里的构造函数仅仅初始化字段属性,并没有给字段赋值,仅仅是初始化了字段的名称、类型

ObjectStreamClass

这个类主要用来提取序列化过程中某个对象所属类的元数据信息,对象所属类包含的元数据信息比起它的成员属性包含的元数据信息要复杂许多。

成员属性

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
// 类的基本信息
private Class<?> cl; // 1. Class
private String name; // 2. cl.getName()
private volatile Long suid; // 3. serialVersionUID

private boolean isProxy; // 4. Proxy.isProxyClass(cl)
private boolean isEnum; // 5. Enum.class.isAssignableFrom(cl)
private boolean serializable; // 6. Serializable.class.isAssignableFrom(cl)
private boolean externalizable; // 7. Externalizable.class.isAssignableFrom(cl)

// Serializable 接口默认的方法,通过反射调用
private Constructor<?> cons; // 默认的构造函数
private Method writeObjectMethod; // writeObject
private Method readObjectMethod; // readObject
private Method readObjectNoDataMethod; // readObjectNoData
private Method writeReplaceMethod; // writeReplace
private Method readResolveMethod; // readResolve
private boolean hasWriteObjectData; // writeObjectMethod!=null

// localDesc表示本类的描述信息,superDesc表示父类的描述信息
private ObjectStreamClass localDesc; // this
private ObjectStreamClass superDesc; // 父类,superDesc=ObjectStreamClass.lookup(superCl, false)

// 要序列化的字段信息,通过 getSerialFields(c1) 获取
private ObjectStreamField[] fields; // 序列化的字段信息

再提一下lookup方法:

1
2
3
4
5
6
7
8
// false 表示只获取实现了 Serializable 接口的类
public static ObjectStreamClass lookup(Class<?> cl) {
return lookup(cl, false);
}
// true 表示不管是否实现这个接口都提取相关的信息
public static ObjectStreamClass lookupAny(Class<?> cl) {
return lookup(cl, true);
}

总结

借用binarylei师傅的图:

image-20210716150301931

每一个序列化结果中,都先包含一段类描述信息,然后才是对象的信息。