Java中序列化实现原理研究
1.什么是序列化和反序列化
- 序列化
是指将Java对象保存为二进制字节码的过程。 - 反序列化
将二进制字节码重新转成Java对象的过程。
2.为什么序列化
- 我们知道,一般Java对象的生命周期比Java虚拟机短,而实际的开发中,我们需要
在Jvm停止后能够继续持有对象,这个时候就需要用到序列化技术将对象持久到磁盘或数据库。 - 在多个项目进行RPC调用的,需要在网络上传输JavaBean对象。我们知道数据只能以二进制的
形式才能在网络上进行传输。所以也需要用到序列化技术。
3.序列化的底层原理
- 程序入口
Student stu1 = new Student(1001, "jack", "play");Student stu2 = new Student(1002, "tom", "sleep");ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\stu.dat"));oos.writeObject(stu1);oos.writeObject(stu2);oos.close();
- 序列化
- 在调用writeObject()方法之前,会先调用ObjectOutputStream的构造函数,生成
一个ObjectOutputStream对象。
public ObjectOutputStream(OutputStream out) throws IOException {verifySubclass();// bout是底层的数据字节容器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;}}
writeStreamHeader的方法内容如下:
protected void writeStreamHeader() throws IOException {bout.writeShort(STREAM_MAGIC);bout.writeShort(STREAM_MAGIC);}
其中STREAM_MAGIC和STREAM_MAGIC所代表的是序列化的标识和版本,其具体内容可在ObjectStreamConstants接口中找到
/*** Magic number that is written to the stream header.*/final static short STREAM_MAGIC = (short)0xaced;/*** Version number that is written to the stream header.*/final static short STREAM_VERSION = 5;
- 调用writeObject()方法进行具体的序列化写入
public final void writeObject(Object obj) throws IOException {if (enableOverride) {writeObjectOverride(obj);return;}try {// 这个方法里面具体是调用writeObject0()方法进行具体的序列化操作writeObject0(obj, false);} 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 objectsint 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 objectObject orig = obj;// 需要序列的对象的Class对象Class<?> cl = obj.getClass();ObjectStreamClass desc;for (;;) {// 提示:跳过检查string和数组// REMIND: skip this check for strings/arrays?Class<?> repCl;// 创建描述c1的ObjectStreamClass对象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 timeif (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// 根据实际要写入的类型,进行不同的写入操作// 由此可以看出String、Array、Enum是直接写入操作的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) {// 实现序列化接口的都会执行下面的方法// 从这里也可以看出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);}}
从上面可以看出主要做了两件事
1、创建了ObjectStreamClass对象
2、根据实际要写入的类型,进行不同的写入操作
- 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();// 写入Object的标记位符号,表示这是一个新的Object对象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();}}}
/*** new Class Descriptor.*/final static byte TC_CLASSDESC = (byte)0x72;/*** new Object.*/final static byte TC_OBJECT = (byte)0x73;/*** new String.*/final static byte TC_STRING = (byte)0x74;/*** new Array.*/final static byte TC_ARRAY = (byte)0x75;/*** Reference to Class.*/final static byte TC_CLASS = (byte)0x76;
- 接下来会调用writeClassDesc()方法写入被序列化对象的类的类元数据,writeClassDesc()如下:
/*** Writes representation of given class descriptor to stream.*/private void writeClassDesc(ObjectStreamClass desc, boolean unshared)throws IOException{int handle;if (desc == null) {// 写入nullwriteNull();} else if (!unshared && (handle = handles.lookup(desc)) != -1) {writeHandle(handle);} else if (desc.isProxy()) {writeProxyDesc(desc, unshared);} else {writeNonProxyDesc(desc, unshared);}}
writeNull()内容分析:
/*** Writes null code to stream.*/private void writeNull() throws IOException {// TC_NULLbout.writeByte(TC_NULL);}/*** Null object reference.*/final static byte TC_NULL = (byte)0x70;
- 在不为Null的情况下,一般则会执行writeNonProxyDesc()方法
/*** Writes class descriptor representing a standard (i.e., not a dynamic* proxy) class to stream.*/private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)throws IOException{// 类元信息的标记位// 表示接下来的数据代表新的Class描述符bout.writeByte(TC_CLASSDESC);handles.assign(unshared ? null : desc);if (protocol == PROTOCOL_VERSION_1) {// do not invoke class descriptor write hook with old protocoldesc.writeNonProxy(this);} else {// 一般会执行此方法writeClassDescriptor(desc);}Class<?> cl = desc.forClass();bout.setBlockDataMode(true);if (cl != null && isCustomSubclass()) {ReflectUtil.checkPackageAccess(cl);}annotateClass(cl);bout.setBlockDataMode(false);// TC_ENDBLOCKDATA = (byte)0x78;// 表示对一个object的描述块的结束bout.writeByte(TC_ENDBLOCKDATA);writeClassDesc(desc.getSuperDesc(), false);}
调用写入类元信息的writeClassDescriptor()
protected void writeClassDescriptor(ObjectStreamClass desc)throws IOException{desc.writeNonProxy(this);}
写入类元方法最后调用的是writeNonProxy()方法
/*** 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) {// 一般会走这一步,标识序列化// final static byte SC_SERIALIZABLE = 0x02;flags |= ObjectStreamConstants.SC_SERIALIZABLE;}if (hasWriteObjectData) {// final static byte SC_WRITE_METHOD = 0x01;// 自定义writeObject方法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];// 写入字段类型对应的Code(详细见下面)out.writeByte(f.getTypeCode());// 写入字段的名字out.writeUTF(f.getName());if (!f.isPrimitive()) {// 如果不是原始类型(即就是对象或Interface),则会写入表示对象的字符串out.writeTypeString(f.getTypeString());}}}
类型对应表
B -> byteC -> charD -> doubleF -> floatI -> intJ -> longL -> class or interfaceS -> shortZ -> boolean[ -> array
1、调用writeUTF()方法写入对象所属类的名字,对于本例中name = com.beautyboss.slogen.TestObject.对于writeUTF()这个方法,
在写入实际的数据之前会先写入name的字节数。
2、接下来会调用writeLong()方法写入类的序列号UID,UID是通过getSerialVersionUID()方法来获取。
3、 接着会判断被序列化的对象所属类的flag,并写入底层字节容器中(占用两个字节)。类的flag分为以下几类:
这是是0x02,即序列化接口。
/*** Bit mask for ObjectStreamClass flag. Indicates class is Serializable.* 序列化接口的*/final static byte SC_SERIALIZABLE = 0x02;/*** Bit mask for ObjectStreamClass flag. Indicates class is Externalizable.* 自定义序列化接口中writeObject等方法*/final static byte SC_EXTERNALIZABLE = 0x04;/*** Bit mask for ObjectStreamClass flag. Indicates class is an enum type.* @since 1.5 * 枚举类型*/final static byte SC_ENUM = 0x10;
注意:writeClassDesc()是一个递归方法,之后会传入其基类,并进行基类的类元信息数据表的写入。直到找到递归程序的出口(规律是从子类—>父类—>null)。
之后会继续上面的代码(写入序列化对象的实例数据):
/*** 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();}}}
- 写入实例化数据,通过调用writeSerialData()方法
/*** Writes instance data for each serializable class of given object, from* superclass to subclass.*/private void writeSerialData(Object obj, ObjectStreamClass desc)throws IOException{// 获取序列化对象的数据布局ClassDataSlot,从父类那继承过来的在前面ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();for (int i = 0; i < slots.length; i++) {ObjectStreamClass slotDesc = slots[i].desc;// 如果序列化对象实现了自己的writeObject()方法,则进入if,否则进入else,执行默认的写入方法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);}}}
// 默认的写入实例数据
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];}// 获取对象中基本类型的实例数据,并将其放到primVals数组中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);// 将对应的对象类型字段保存到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 {// 此时的值是:["paly", 1001,"tom"]// 对序列化对象中引用类型的字段,递归调用writeObject0()写入对应的数据writeObject0(objVals[i],fields[numPrimFields + i].isUnshared());} finally {if (extendedDebugInfo) {debugInfoStack.pop();}}}}
这个方法中做了:
1、获取序列化类对象的基本类型的实例数据,并写入到底层的字节数据容器中去;
2、获取对应类的引用类型(非基本类型)的字段成员,递归调用writeObject0()方法写入实例数据。
从上得知,写入数据是从父类到子类的顺序来写的。
- 思路图
4.序列化和单例模式
- 所谓单例:就是单例模式就是在整个全局中(无论是单线程还是多线程),该对象只存在一个实例,而且只应该存在一个实例,没有副本。
- 序列化对单例有破坏
1、通过对某个对象的序列化与反序列化得到的对象是一个新的对象,这就破坏了单例模式的单例性。
2、我们知道readObject()的时候,底层运用了反射的技术,
序列化会通过反射调用无参数的构造方法创建一个新的对象。
这破坏了对象的单例性。
3、解决方案:
在需要的单例的对象类中添加
private Object readResolve() {return singleton;}}
hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true
invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。
5.为什么说序列化并不安全
- 因为序列化的对象数据转换为二进制,并且完全可逆。但是在RMI调用时
所有private字段的数据都以明文二进制的形式出现在网络的套接字上,这显然是不安全的 - 解决方案
1、 序列化Hook化(移位和复位)
2、 序列数据加密和签名
3、 利用transient的特性解决
4、 打包和解包代理
6.补充
- static和transient字段不能被序列化(感兴趣的同学可以深入研究下)
Java中序列化实现原理研究相关推荐
- Java中随机数的原理,以及使用时的注意点
转载自 Java中随机数的原理,以及使用时的注意点 1 前言 一提到 Java 中的随机数,很多人就会想到 Random,当出现生成随机数这样需求时,大多数人都会选择使用 Random 来生成随机 ...
- java中序列化与反序列化_Java中的序列化
java中序列化与反序列化 Java提供了一种称为序列化的机制,以按字节的有序或字节序列的形式持久化Java对象,其中包括对象的数据以及有关对象的类型和存储在对象中的数据类型的信息. 因此,如果我们已 ...
- java中序列化之子类继承父类序列化
原文 父类实现了Serializable,子类不需要实现Serializable 相关注意事项 a)序列化时,只对对象的状态进行保存,而不管对象的方法: b)当一个父类实现序列化,子类 ...
- Java 中序列化与反序列化
一. 序列化和反序列化概念 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.将程序中的对象,放入文 ...
- java中JVM的原理【转】
一.java虚拟机的生命周期: Java虚拟机的生命周期 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序.程序开始执行时他才运行,程序结束时他就停止.你在同一台机器上运行三个程序,就会 ...
- Java中的锁原理、锁优化、CAS、AQS详解
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:景小财 www.jianshu.com/p/e674ee68 ...
- Java中的锁[原理、锁优化、CAS、AQS]
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:用好Java中的枚举,真的没有那么简单!个人原创+1博客:点击前往,查看更多 作者:高广超 链接:https:/ ...
- Java中的锁原理、锁优化、CAS、AQS详解!
阅读本文大概需要 2.8 分钟. 来源:jianshu.com/p/e674ee68fd3f 一.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 二.锁实现的基本原理 2.1.v ...
- Java基础—序列化底层原理
原文地址:https://blog.csdn.net/xlgen157387/article/details/79840134 目录 一.基本概念 二.Java如何实现序列化和反序列化 三.相关注意事 ...
最新文章
- python代码大全表解释-python中的字典用法大全的代码
- java map 查找_在Java TreeMap中查找元素位置
- onclick函数的导包问题
- 算法练习day11——190329(平衡二叉树、搜索二叉树、完全二叉树)
- 不再内卷!视觉字幕化新任务合集
- linux文件类型elf,[Linux]四种ELF对象文件类型
- java 导出excel二维表,如何轻松将EXCEL二维统计表转为数据清单?
- linux安装pip
- Oracle的安装、配置与工具使用 实验笔记一
- 语音识别怎么最终识别出字?
- MacBook2016在SSD上安装Win To Go(成功经验分享)
- 向量的表示及协方差矩阵
- Java八股文(高阶)背诵版
- 选址(重心法、微分法迭代)
- android apk如何压缩包,Android 打包Apk太大 如何进行压缩APK文件
- python官网学习爬虫资料_Python爬虫学习?
- 分布式系统生成唯一主键
- C语言零基础项目:打字母游戏!详细思路+源码分享
- java编程将HTML文件转换成PDF文件
- 天才小毒妃 第966章 不死不灭的痛苦