Java对象的序列化/反序列化原理及源码解析(上)

简介: Java对象的序列化/反序列化原理及源码解析(上)

https://github.com/Wasabi1234/Java-Interview-Tutorial

1 什么是序列化、反序列化

Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。

2 为什么需要序列化?

2.1 使用场景

2.1.1 持久化对象

JVM允许我们在内存中创建可复用的Java对象,但一般只有当JVM处于运行时,这些对象才可能存在。即这些对象的生命周期不会比JVM的生命周期更长。

但在现实应用中,可能要求在JVM停止运行之后能够持久化指定对象,并在将来某时重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,后面再将这些字节组装成对象。

2.1.2 网络传输

把Java对象通过网络进行传输时。因为数据只能以二进制形式在网络中传输,所以当把对象通过网络发出去前,需要先序列化成二进制数据,在接收端读到二进制数据后反序列化成Java对象。

对象序列化保存的是对象的”状态”,即其成员变量,所以对象序列化不会关注静态变量。 除了在持久化对象时会用到对象序列化,当使用RMI或在网络中传递对象时,都会用到对象序列化。

Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

3 如何使用?

以序列化到文件为例,看看Java序列化的基本用法。

package test;
import java.io.*;
public class SerializableTest {
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream("temp.out");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        TestObject testObject = new TestObject();
        oos.writeObject(testObject);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream("temp.out");
        ObjectInputStream ois = new ObjectInputStream(fis);
        TestObject deTest = (TestObject) ois.readObject();
        System.out.println(deTest.testValue);
        System.out.println(deTest.parentValue);
        System.out.println(deTest.innerObject.innerValue);
    }
}
class Parent implements Serializable {
    private static final long serialVersionUID = -4963266899668807475L;
    public int parentValue = 100;
}
class InnerObject implements Serializable {
    private static final long serialVersionUID = 5704957411985783570L;
    public int innerValue = 200;
}
class TestObject extends Parent implements Serializable {
    private static final long serialVersionUID = -3186721026267206914L;
    public int testValue = 300;
    public InnerObject innerObject = new InnerObject();
}

程序执行完用sublime打开temp.out文件,可以看到

aced 0005 7372 0017 636f 6d2e 7373 732e
7465 7374 2e54 6573 744f 626a 6563 74d3
c67e 1c4f 132a fe02 0002 4900 0974 6573
7456 616c 7565 4c00 0b69 6e6e 6572 4f62
6a65 6374 7400 1a4c 636f 6d2f 7373 732f
7465 7374 2f49 6e6e 6572 4f62 6a65 6374
3b78 7200 1363 6f6d 2e73 7373 2e74 6573
742e 5061 7265 6e74 bb1e ef0d 1fc9 50cd
0200 0149 000b 7061 7265 6e74 5661 6c75
6578 7000 0000 6400 0001 2c73 7200 1863
6f6d 2e73 7373 2e74 6573 742e 496e 6e65
724f 626a 6563 744f 2c14 8a40 24fb 1202
0001 4900 0a69 6e6e 6572 5661 6c75 6578
7000 0000 c8

4 序列化原理

调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()后,究竟发生什么?

4.1 ObjectStream Class

类的序列化描述符,该类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可通过lookup()方法来查找、创建在这个JVM中加载的特定的ObjectStreamClass对象。

4.2 序列化 - writeObject()

调用wroteObject进行序列化前会先调用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();
  // flush数据
  bout.setBlockDataMode(true);
  if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
  } else {
        debugInfoStack = null;
  }
}

构造器首先把bout绑定到底层的字节数据容器,接着会调用writeStreamHeader:提供了writeStreamHeader方法,于是子类可将自己的头部追加或添加到流中。 将魔数和版本写入流中。

1.png

2.png

接着调用writeObject执行序列化:

将指定对象写入ObjectOutputStream。 写入对象的类,类的签名以及该类及其所有父类的非transient和非静态字段的值。 可以使用writeObject和readObject方法覆盖类的默认序列化。 该对象引用的对象是可传递的,因此ObjectInputStream可以重建对象的完整等效图。

对于OutputStream的问题和不应序列化的类,将引发异常。 所有异常对于OutputStream都是致命的,OutputStream处于不确定状态,并且取决于调用者忽略还是恢复流状态

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()进行序列化:

基础writeObject / writeUnshared实现

  private void writeObject0(Object obj, boolean unshared) {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // 处理先前编写的和不可替换的对象
            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;
            }
            // 检查替换对象
            Object orig = obj;
            // 要序列化的对象的Class对象
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                 // 创建描述cl的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 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) {
                // 被序列化对象实现了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);
        }
    }

生成一个描述被序列化对象类的类元信息的ObjectStreamClass对象

根据传入的需要序列化的对象的实际类型进行不同的序列化操作。从代码里面可以很明显的看到,

对于String类型、数组类型和Enum可以直接进行序列化

如果被序列化对象实现了Serializable对象,则会调用writeOrdinaryObject()方法进行序列化

这里可以解释一个问题:Serializbale接口是个空的接口,并没有定义任何方法,为什么需要序列化的接口只要实现Serializbale接口就能够进行序列化。

答案是:Serializable接口这是一个标识,告诉程序所有实现了”我”的对象都需要进行序列化。


因此,序列化过程接下来会执行到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标志位
            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();
            }
        }
    }

在这个方法中首先会往底层字节容器中写入TC_OBJECT,表示这是一个新的Object

/**
 * new Object.
 */
final static byte TC_OBJECT =       (byte)0x73;

接下来会调用writeClassDesc()方法写入被序列化对象的类的类元数据,writeClassDesc()方法实现如下:

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    int handle;
    if (desc == null) {
        // 如果desc为null
        writeNull();
    } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
        writeHandle(handle);
    } else if (desc.isProxy()) {
        writeProxyDesc(desc, unshared);
    } else {
        writeNonProxyDesc(desc, unshared);
    }
}

在这个方法中会先判断传入的desc是否为null,如果为null则调用writeNull()方法

private void writeNull() throws IOException {
    // TC_NULL =         (byte)0x70;
    // 表示对一个Object引用的描述的结束
    bout.writeByte(TC_NULL);
}

如果不为null,则一般情况下接下来会调用writeNonProxyDesc()方法,该方法实现如下:

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    // TC_CLASSDESC =    (byte)0x72;
    // 表示一个新的Class描述符
    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);
}

在这个方法中首先会写入一个字节的TC_CLASSDESC,这个字节表示接下来的数据是一个新的Class描述符,接着会调用writeNonProxy()方法写入实际的类元信息,writeNonProxy()实现如下:

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); // 写入类的flag
    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()) {
            // 如果不是原始类型,即是对象或者Interface
            // 则会写入表示对象或者类的类型字符串
            out.writeTypeString(f.getTypeString());
        }
    }
}
目录
相关文章
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
528 2
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
459 0
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
657 1
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
577 1
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
765 24
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
510 0
|
安全 Java
Java并发编程笔记之CopyOnWriteArrayList源码分析
并发包中并发List只有CopyOnWriteArrayList这一个,CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行修改操作和元素迭代操作都是在底层创建一个拷贝数组(快照)上进行的,也就是写时拷贝策略。
19783 0
|
Java 安全
Java并发编程笔记之读写锁 ReentrantReadWriteLock 源码分析
我们知道在解决线程安全问题上使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而实际情况下会有写少读多的场景,显然 ReentrantLock 满足不了需求,所以 ReentrantReadWriteLock 应运而生,ReentrantReadWriteLock 采用读写分离,多个线程可以同时获取读锁。
3402 0