众所周知,Java 原生的序列化方法可以分为两种:
实际上,Externalizable接口继承自Serializable接口,但他们的序列化机制是完全不同的:使用Serializable的方式,在反序列化时不会直接调用被序列化对象的构造器,而是先获取被序列化对象对应类的 【自下而上最顶层实现了Serializable的祖先类的超类】【即自上而下连续的最后一个未实现Serizable接口的类】的构造器,然后在此构造器的基础上重新创建一个新的构造器来完成实例化。这句话读起来有些拗口,我们后面分析Serializable反序列化机制时还会详细介绍。而使用Externalizable则是调用一个无参构造方法来实例化,原因如下:
Externalizable序列化的过程:使用Externalizable序列化时,在进行反序列化的时候,会重新实例化一个对象,然后再将被反序列化的对象的状态全部复制到这个新的实例化对象当中去,这也就是为什么会调用构造方法啦,也因此必须有一个无参构造方法供其调用,并且权限是public。
对象反序列化时通过构造函数来实例化对象是很直观的容易理解的方式,而实现Serializable接口的方式在反序列化时却不会直接调用被序列化对象的构造器,JVM为什么要这么做呢?个人认为是为了让一些没有无参构造方法的类也能实现序列化和反序列化(Gson sun.misc.Unsafe.allocateInstance序列化Java对象这篇博文中就讲到,Fastjson和Jackson都无法将没有无参构造方法的类对象反序列化,而Gson底层通过调用Unsafe.allocateInstance方法来反序列化没有无参构造方法的类对象。这才是真正意义上的不使用构造器实现类对象反序列化的机制,后面还会再介绍)。下面通过分析ObjectInputStream的源码来梳理实现Serializable接口的反序列化方式。
我们通过ObjectInputStream的readObject()方法来实现对象的反序列化,下面为该方法的源码:
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false); //实际的实现方法
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
其中readObject0(false)为readObject()的实际实现方法:
/**
* Underlying readObject implementation.
*/
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc; //存储对象类型标志
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
totalObjectRefs++;
try {
switch (tc) { //按存储对象类型标志分case读取
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY: //数组
return checkResolve(readArray(unshared));
case TC_ENUM: //枚举
return checkResolve(readEnum(unshared));
case TC_OBJECT: //普通对象
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
该方法先从输入流中读取对象的类型标志,以普通对象Object为例,会调用java.io.ObjectInputStream.checkResolve(java.lang.Object),而在上面的代码中可以看到,checkResolve()方法的入参为readOrdinaryObject(unshared),实际上就是在readOrdinaryObject(boolean)方法中完成的实例化。我们接着看readOrdinaryObject(boolean)的源码:
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false); //1.读取类描述信息
desc.checkDeserialize();//2.check该类能否反序列化,判断条件为是否实现Serializable或Externalizable其一
Class> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;//3.实例化对象
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc); //4.对象赋值
} else {
readSerialData(obj, desc); //4.对象赋值
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
在上述代码中最关键的两个步骤:1.读取类描述信息;3.实例化对象。为了便于理解,我们先来看第三步中实例化对象的方法desc.newInstance()。该方法在类描述信息java.io.ObjectStreamClass类中。
/**
* Creates a new instance of the represented class. If the class is
* externalizable, invokes its public no-arg constructor; otherwise, if the
* class is serializable, invokes the no-arg constructor of the first
* non-serializable superclass. Throws UnsupportedOperationException if
* this class descriptor is not associated with a class, if the associated
* class is non-serializable or if the appropriate no-arg constructor is
* inaccessible/unavailable.
*/
Object newInstance()
throws InstantiationException, InvocationTargetException,
UnsupportedOperationException
{
requireInitialized();
if (cons != null) {
try {
if (domains == null || domains.length == 0) {
return cons.newInstance(); //调用构造器实例化对象
} else {
JavaSecurityAccess jsa = SharedSecrets.getJavaSecurityAccess();
PrivilegedAction> pea = () -> {
try {
return cons.newInstance();
} catch (InstantiationException
| InvocationTargetException
| IllegalAccessException x) {
throw new UndeclaredThrowableException(x);
}
}; // Can't use PrivilegedExceptionAction with jsa
try {
return jsa.doIntersectionPrivilege(pea,
AccessController.getContext(),
new AccessControlContext(domains));
} catch (UndeclaredThrowableException x) {
Throwable cause = x.getCause();
if (cause instanceof InstantiationException)
throw (InstantiationException) cause;
if (cause instanceof InvocationTargetException)
throw (InvocationTargetException) cause;
if (cause instanceof IllegalAccessException)
throw (IllegalAccessException) cause;
// not supposed to happen
throw x;
}
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {//构造器为空则抛异常,反序列化失败
throw new UnsupportedOperationException();
}
}
由上述代码可知,desc.newInstance()方法通过调用cons.newInstance()方法完成实例化。其实,从该方法上面的那段注释我们可以了解到,如果反序列化类实现了externalizable,则这里调用的就是权限为public的无参构造函数;否则如果反序列化类实现了serializable,则这里调用的就是第一个没有实现serializable接口的父类的无参构造器。
到这里我们可以确认的是,实现Serializable接口的方式反序列化本质上同样是利用构造器来完成的实例化。而为了证实代码注释中所说的事实,我们需要回过头去看第一步中读取类描述信息的readClassDesc(boolean unshared)方法:
private ObjectStreamClass readClassDesc(boolean unshared)
throws IOException
{
byte tc = bin.peekByte();
ObjectStreamClass descriptor;
switch (tc) {
case TC_NULL:
descriptor = (ObjectStreamClass) readNull();
break;
case TC_REFERENCE:
descriptor = (ObjectStreamClass) readHandle(unshared);
break;
case TC_PROXYCLASSDESC:
descriptor = readProxyDesc(unshared);//读取代理类描述信息
break;
case TC_CLASSDESC:
descriptor = readNonProxyDesc(unshared);//读取非代理类描述信息
break;
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
if (descriptor != null) {
validateDescriptor(descriptor);
}
return descriptor;
}
以读取非代理类描述信息方法readNonProxyDesc(boolean)为例:
private ObjectStreamClass readNonProxyDesc(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_CLASSDESC) {
throw new InternalError();
}
ObjectStreamClass desc = new ObjectStreamClass();
int descHandle = handles.assign(unshared ? unsharedMarker : desc);
passHandle = NULL_HANDLE;
ObjectStreamClass readDesc = null;
try {
readDesc = readClassDescriptor();
} catch (ClassNotFoundException ex) {
throw (IOException) new InvalidClassException(
"failed to read class descriptor").initCause(ex);
}
Class> cl = null;
ClassNotFoundException resolveEx = null;
bin.setBlockDataMode(true);
final boolean checksRequired = isCustomSubclass();
try {
if ((cl = resolveClass(readDesc)) == null) {
resolveEx = new ClassNotFoundException("null class");
} else if (checksRequired) {
ReflectUtil.checkPackageAccess(cl);
}
} catch (ClassNotFoundException ex) {
resolveEx = ex;
}
// Call filterCheck on the class before reading anything else
filterCheck(cl, -1);
skipCustomData();
try {
totalObjectRefs++;
depth++;
desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));//初始化非代理类描述对象
} finally {
depth--;
}
handles.finish(descHandle);
passHandle = descHandle;
return desc;
}
我们接着看initNonProxy(ObjectStreamClass model, Class> cl, ClassNotFoundException resolveEx, ObjectStreamClass superDesc)方法:
void initNonProxy(ObjectStreamClass model,
Class> cl,
ClassNotFoundException resolveEx,
ObjectStreamClass superDesc)
throws InvalidClassException
{
long suid = Long.valueOf(model.getSerialVersionUID());
ObjectStreamClass osc = null;
if (cl != null) {
osc = lookup(cl, true); //查找并返回非代理类描述对象
if (osc.isProxy) {
throw new InvalidClassException(
"cannot bind non-proxy descriptor to a proxy class");
}
if (model.isEnum != osc.isEnum) {
throw new InvalidClassException(model.isEnum ?
"cannot bind enum descriptor to a non-enum class" :
"cannot bind non-enum descriptor to an enum class");
}
if (model.serializable == osc.serializable &&
!cl.isArray() &&
suid != osc.getSerialVersionUID()) {
throw new InvalidClassException(osc.name,
"local class incompatible: " +
"stream classdesc serialVersionUID = " + suid +
", local class serialVersionUID = " +
osc.getSerialVersionUID());
}
if (!classNamesEqual(model.name, osc.name)) {
throw new InvalidClassException(osc.name,
"local class name incompatible with stream class " +
"name \"" + model.name + "\"");
}
if (!model.isEnum) {
if ((model.serializable == osc.serializable) &&
(model.externalizable != osc.externalizable)) {//一个类不能同时实现serializable和externalizable
throw new InvalidClassException(osc.name,
"Serializable incompatible with Externalizable");
}
if ((model.serializable != osc.serializable) ||
(model.externalizable != osc.externalizable) ||
!(model.serializable || model.externalizable)) {//如果一个类既没实现serializable,也没实现externalizable,则给该类的描述对象的成员变量deserializeEx赋值,该值不为空则标志该类不能反序列化
deserializeEx = new ExceptionInfo(
osc.name, "class invalid for deserialization");
}
}
}
this.cl = cl;
this.resolveEx = resolveEx;
this.superDesc = superDesc;
name = model.name;
this.suid = suid;
isProxy = false;
isEnum = model.isEnum;
serializable = model.serializable;
externalizable = model.externalizable;
hasBlockExternalData = model.hasBlockExternalData;
hasWriteObjectData = model.hasWriteObjectData;
fields = model.fields;
primDataSize = model.primDataSize;
numObjFields = model.numObjFields;
if (osc != null) {
localDesc = osc;
writeObjectMethod = localDesc.writeObjectMethod;
readObjectMethod = localDesc.readObjectMethod;
readObjectNoDataMethod = localDesc.readObjectNoDataMethod;
writeReplaceMethod = localDesc.writeReplaceMethod;
readResolveMethod = localDesc.readResolveMethod;
if (deserializeEx == null) {
deserializeEx = localDesc.deserializeEx;
}
domains = localDesc.domains;
cons = localDesc.cons;
}
fieldRefl = getReflector(fields, localDesc);
// reassign to matched fields so as to reflect local unshared settings
fields = fieldRefl.getFields();
initialized = true;
}
我们接着看lookup(Class> cl, boolean all)方法:
static ObjectStreamClass lookup(Class> cl, boolean all) {
if (!(all || Serializable.class.isAssignableFrom(cl))) {
return null;
}
processQueue(Caches.localDescsQueue, Caches.localDescs);
WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
Reference> ref = Caches.localDescs.get(key);
Object entry = null;
if (ref != null) {
entry = ref.get();
}
EntryFuture future = null;
if (entry == null) {
EntryFuture newEntry = new EntryFuture();
Reference> newRef = new SoftReference<>(newEntry);
do {
if (ref != null) {
Caches.localDescs.remove(key, ref);
}
ref = Caches.localDescs.putIfAbsent(key, newRef);
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); //1.实例化类描述对象
} catch (Throwable th) {
entry = th;
}
if (future.set(entry)) {
Caches.localDescs.put(key, new SoftReference
在上述代码中,先调用构造函数实例化反序列化对象的类描述对象,并返回。我们来看类描述对象的构造函数ObjectStreamClass(final Class> cl):
private ObjectStreamClass(final Class> cl) {
this.cl = cl;
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);//获取Externalizable方式的构造器
} else {
cons = getSerializableConstructor(cl); //获取Serilizable机制的构造器
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
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) {
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;
}
我们重点看下获取Serializable机制的构造器方法getSerializableConstructor(Class> cl):
private static Constructor> getSerializableConstructor(Class> cl) {
Class> initCl = cl;
while (Serializable.class.isAssignableFrom(initCl)) {//1.自底向上查找反序列化类的父类中第一个没有继承Serializable的类
Class> prev = initCl;
if ((initCl = initCl.getSuperclass()) == null ||
(!disableSerialConstructorChecks && !superHasAccessibleConstructor(prev))) {
return null;
}
}
try {
Constructor> cons = initCl.getDeclaredConstructor((Class>[]) null); //2.获取父类中第一个没有继承Serializable的类的无参构造器,如果父类没有无参构造器则抛异常
int mods = cons.getModifiers();
if ((mods & Modifier.PRIVATE) != 0 ||
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
!packageEquals(cl, initCl)))
{
return null;
}
cons = reflFactory.newConstructorForSerialization(cl, cons);//3.基于父类中第一个没有继承Serializable的类的构造器产生一个新的构造器
cons.setAccessible(true);
return cons;
} catch (NoSuchMethodException ex) {
return null;//父类没有无参构造器则返回null,最终会导致类实例化失败
}
}
在上述代码中,重点有三步:第一步,自底向上查找反序列化类的父类中第一个没有继承Serializable的类;第二步,获取第一步中得到的父类无参构造器;第三步,基于第二步得到的无参构造器产生一个新的构造器。我们重点来看第三步(sun.reflect.ReflectionFactory中的newConstructorForSerialization()方法):
public Constructor> newConstructorForSerialization(Class> var1, Constructor> var2) {
return var2.getDeclaringClass() == var1 ? var2 : this.generateConstructor(var1, var2);
}
实际上,上述三元表达式中var2.getDeclaringClass() == var1标志着当前类没有实现Serializable接口,不在正常情况的考虑范围,因而我们重点看generateConstructor(Class> var1, Constructor> var2)方法:
private final Constructor> generateConstructor(Class> var1, Constructor> var2) {
SerializationConstructorAccessorImpl var3 = (new MethodAccessorGenerator()).generateSerializationConstructor(var1, var2.getParameterTypes(), var2.getExceptionTypes(), var2.getModifiers(), var2.getDeclaringClass());
Constructor var4 = this.newConstructor(var2.getDeclaringClass(), var2.getParameterTypes(), var2.getExceptionTypes(), var2.getModifiers(), langReflectAccess().getConstructorSlot(var2), langReflectAccess().getConstructorSignature(var2), langReflectAccess().getConstructorAnnotations(var2), langReflectAccess().getConstructorParameterAnnotations(var2));
this.setConstructorAccessor(var4, var3);
var4.setAccessible(true);
return var4;
}
由该方法可知,我们是将父类的无参构造器作为入参,重新创建一个包含该构造器的新构造器并返回。
总结:
Java 原生的序列化方法可以分为两种,即
两者的序列化机制是完全不同的:使用Serializable的方式,在反序列化时不会直接调用被序列化对象的构造器,而是先获取被序列化对象对应类的 【自下而上最顶层实现了Serializable的祖先类的超类】【即自上而下连续的最后一个未实现Serizable接口的类】的构造器,然后在此构造器的基础上重新创建一个新的构造器来完成实例化。而使用Externalizable是通过调用一个无参构造方法来实例化。
本质上两种方式都是使用类构造器来完成实例化,相对于Externalizable必须要求类实现无参构造器,Serializable放开了这一限制,在一定程度上适用性强于Externalizable,但仍然要求其未实现Serializable接口的父类一定要实现无参构造器,否则还是无法反序列化。另一方面,实现 Externalizable 接口能带来较大的时间及空间的性能提升(相比于Serializable需要构造新的构造器等复杂逻辑,具体参见几种序列化协议的介绍),但由于实现 Externalizable 接口导致了编程复杂度的增加,所以大部分时候都是采用实现 Serializable 接口方式来实现序列化。
此外,还可以通过使用Unsafe的allocateInstance()方法,其实现真正做到了不依赖构造器实现类的实例化。allocateInstance方法为native方法,基于C语言实现,感兴趣的可以阅读参考博客:不调用给定类的构造方法创建给定类的对象和源码分析:Java对象的内存分配。
参考博客:
1、https://blog.csdn.net/baiye_xing/article/details/71809993 【Java深入】序列化详解
2、https://blog.csdn.net/sym90/article/details/41940341 Java 学习之路 之 对象序列化(六十九)
3、https://blog.csdn.net/u013815832/article/details/95212567 Java 序列化和反序列化(一)Serializable 使用场景
4、https://my.oschina.net/valsong/blog/2248616 java 通过Unsafe不使用构造器直接创建对象
5、http://javaweb.org/?p=1872 Gson sun.misc.Unsafe.allocateInstance序列化Java对象
6、 https://blog.csdn.net/amen_wu/article/details/80578849 不通过构造函数也能创建对象吗?
7、https://blog.csdn.net/xlgen157387/article/details/79840134 序列化和反序列化的底层实现原理是什么?
8、https://blog.csdn.net/u014653197/article/details/78114041 JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的
9、https://www.iteye.com/blog/yueyemaitian-2078090 JDK中反序列化对象的过程(ObjectInputStream#readObject)
10、https://blog.csdn.net/zyzzxycj/article/details/89877863 深入理解sun.misc.Unsafe原理
11、https://www.jb51.net/article/140726.htm 一篇看懂Java中的Unsafe类
12、https://blog.csdn.net/weixin_41050155/article/details/82960099 创建对像实例的5种方式
13、https://blog.csdn.net/u011039332/article/details/91345986 不调用给定类的构造方法创建给定类的对象 包含序列化字节示例
14、https://www.cppentry.com/bencandy.php?fid=54&aid=111020&page=2 源码分析:Java对象的内存分配
15、https://www.cnblogs.com/grl214/p/5895854.html java构造器的深入理解
16、https://blog.csdn.net/lb_383691051/article/details/46582109?ref=myread Java构造器(定义,作用,原理)
17、https://blog.csdn.net/baiye_xing/article/details/73249819 几种序列化协议的介绍 有各种协议的时间及空间性能对比
18、https://www.cnblogs.com/senlinyang/p/8204752.html Java序列化机制原理