1.什么是序列化
序列化是将对象的状态转换为欸字节流的过程,反序列化则是将字节流还原成对象。
在进行序列化时会将对象的逻辑状态保存下来,用于在反序列化时恢复对象
2.为什么需要序列化
在 java 中对象等信息保存在 jvm 中,包含引用,内存地址等信息,在进行跨 jvm 传输,环境的变化会导致原有的信息不在可用,内存地址等都发生了变化,所以在进行序列化的时候没有存储物理状态,只保存了逻辑状态用来恢复对象
3.java 中的序列化
3.1 Serializable 只是一个标记
public interface Serializable {}它没有任何方法,是一个标记接口(Marker Interface)。JVM 在序列化时会检查这个标记,没有实现它的类会直接抛出 NotSerializableException。真正干活的是 ObjectOutputStream 和 ObjectInputStream
3.2 序列化协议本身长什么样
用 hexdump 打开一个序列化后的文件,开头总是:
AC ED 00 05 ...-
AC ED:魔数(STREAM_MAGIC),标识这是一个序列化流。 -
00 05:版本号(STREAM_VERSION)。
后面跟着的是一系列类型标记字节,比如:
-
73(TC_OBJECT):表示接下来是一个对象。 -
72(TC_CLASSDESC):表示接下来是一个类描述符(类名、serialVersionUID、字段描述等)。 -
78(TC_ENDBLOCKDATA):自定义数据块结束
3.3 writeObject / readObject:真正的入口
默认情况下,ObjectOutputStream/ObjectInputStream 会通过反射自动读写一个类里所有非 transient、非 static 的字段。但如果类里自己定义了这两个私有方法:
private void writeObject(ObjectOutputStream out) throws IOException;private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;JVM 就会调用这两个自定义方法,而不是走默认逻辑
3.4 readObject 的默认行为:defaultReadObject
即使自定义了 readObject,通常也会在里面调用:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 走默认的字段读取逻辑 // 自定义的额外逻辑...}在学习反序列化链子时,很多时候 defaultReadObject()后就是值得关注的地方,因为在 defaultObject()对字段进行填充后往往就要对状态进行恢复,就会触发许多回调方法,就有可能被攻击者利用
3.5 readResolve / writeReplace:对象替换钩子
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;-
writeReplace:序列化时,如果对象定义了这个方法,序列化流程会先调用它,用它的返回值替换掉原对象去序列化。 -
readResolve:反序列化重建出对象后,如果定义了这个方法,会用它的返回值替换掉刚重建出的对象,作为最终返回给调用者的对象。
3.7 Externalizable
java
public interface Externalizable extends Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;}区别于 Serializable 依赖反射自动读写字段,Externalizable 要求开发者完全手动实现读写逻辑,而且这两个方法是 public 的、在接口里显式声明的(不是约定,是真正的接口方法)。
-
对于实现了
Serializable但没有实现Externalizable的类,反序列化重建对象时,不会调用该类自己的构造器,而是通过 JVM 内部的 native 机制(sun.reflect.ReflectionFactory#newConstructorForSerialization,底层最终依赖Unsafe分配内存)直接分配对象内存,跳过了包括无参构造器在内的所有构造逻辑。 -
但如果父类没有实现
Serializable,那么会调用到这个祖先类的无参构造器