JDK的对象序列化

1.概述

1.1.序列化框架

对象的引用关系,组成了一个图结构,对象即节点,对象之间的引用关系则为边.

对象序列化,就是通过一定的算法,把对象图转化成字节序列,用于网络中传输.

对象序列化框架通常需要具备3个能力:

  • 针对对象的继承和组合关系,制定合理的序列化算法


    image.png
  • 具备处理对象图中,对同一对象多次引用的能力
  • 可以支持用户自主选择要序列化的数据集合

1.2.JDK的对象序列化

JDK中主要有3个类:

  • ObjectOutputStream:负责序列化一个对象
  • ObjectInputStream:负责读取字节流,反序列成对象
  • ObjectStreamClass:用于描述一个Class对象结构如何被序列化和反序列化

下面重点讲解ObjectStreamClass类型序列化描述符的作用,以及ObjectOutputStream的具体序列化算法的实现.

ObjectInputStream的反序列化逻辑,可以理解为是一个逆向操作,不再赘述.

2.序列化规范

2.1.类型序列化描述符(ObjectStreamClass)详解

字段定义如下:

    /**
     * 类对象
     */
    private Class<?> cl;
    /**
     * 全限定类名
     */
    private String name;
    /**
     * 是否jdk代理类
     */
    private boolean isProxy;
    /**
     * 是否枚举类
     */
    private boolean isEnum;
    /**
     * 类继承体系中,是否存在某个类实现了Serializable接口
     */
    private boolean serializable;
    /**
     * 类继承体系中,是否存在某个类实现了Externalizable接口
     */
    private boolean externalizable;
    /**
     * 当前类描述符,即this
     */
    private ObjectStreamClass localDesc;
    /**
     * 直接父类描述符
     */
    private ObjectStreamClass superDesc;
    /**
     * 定义序列化格式的版本
     * 对应private static final serialVersionUID字段值
     * 如果不声明,则会通过默认逻辑计算
     */
    private volatile Long suid;
    /**
     * 可序列化字段
     * 可以通过 private static final ObjectStreamField[] serialPersistentFields;进行主动声明
     * 也可以走默认反射逻辑,筛选所有声明的非static且非transient的字段
     */
    private ObjectStreamField[] fields;
    /**
     * 基本类型字段占用的字节数
     */
    private int primDataSize;
    /**
     * 引用类型字段的个数
     */
    private int numObjFields;
    /**
     * 反序列化时使用的构造方法
     */
    private Constructor<?> cons;
    /**
     * private void writeObject(ObjectOutputStream)方法,必须非static
     * 显示定义的序列化逻,如果不定义,则执行默认的字段序列化逻辑
     */
    private Method writeObjectMethod;
    /**
     * private void readObject(ObjectInputStream)方法,必须非static
     */
    private Method readObjectMethod;
    /**
     * private void readObjectNoData()方法,必须非static
     */
    private Method readObjectNoDataMethod;
    /**
     * 是否存在
     * writeObject(ObjectOutputStream)方法
     */
    private boolean hasWriteObjectData;
    /**
     * Object writeReplace()
     * 当对象要被序列化时,它可以返回一个新的对象,用于序列化
     * 方法必须是具体的实例方法
     * 针对访问权限有特殊要求:
     * 当前类和父类都允许public或protected,
     * 另外额外允许当前类为private的
     * 或者方法为默认包权限
     */
    private Method writeReplaceMethod;
    /**
     * Object readResolve()
     */
    private Method readResolveMethod;
    /**
     * 字段反射器,用来读写可序列化字段的值
     * 包括基本类型值和引用对象值
     */
    private FieldReflector fieldRefl;

    /**
     * 反序列化异常信息
     */
    private ExceptionInfo deserializeEx;
    /**
     * 序列化异常信息
     */
    private ExceptionInfo serializeEx;
    /**
     * 默认序列化异常
     */
    private ExceptionInfo defaultSerializeEx;
    // -----------------------------------------------------------------------------------------------------------------
    /**
     * true if desc has externalizable data written in block data format; this
     * must be true by default to accommodate ObjectInputStream subclasses which
     * override readClassDescriptor() to return class descriptors obtained from
     * ObjectStreamClass.lookup() (see 4461737)
     */
    private boolean hasBlockExternalData = true;
    /**
     * exception (if any) thrown while attempting to resolve class
     */
    private ClassNotFoundException resolveEx;
    /**
     * 数据布局
     * 类数据槽数组,序列化对象的数据布局,被当前类描述
     */
    private volatile ClassDataSlot[] dataLayout;

构造方法逻辑如下:

    /**
     * 构造方法
     * 创建本地类描述符
     *
     * @param cl 指定的类对象
     */
    private ObjectStreamClass(final Class<?> cl) {
        this.cl = cl;
        name = cl.getName();
        // 判断是否jdk代理(Proxy的子类,且Proxy内部缓存包含这个类对象)
        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;

        // 实现了Serializable接口
        if (serializable) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    // 枚举类序列化唯一id为0,没有字段
                    if (isEnum) {
                        suid = 0L;
                        fields = NO_FIELDS;
                        return null;
                    }
                    // 数组类没有字段
                    if (cl.isArray()) {
                        fields = NO_FIELDS;
                        return null;
                    }

                    // 获取声明的serialVersionUID
                    // 可能为null,为null时在调用getter时自动计算默认值保存
                    suid = getDeclaredSUID(cl);
                    try {
                        // 获取序列化字段
                        // private static final ObjectStreamField[] serialPersistentFields
                        fields = getSerialFields(cl);
                        // 计算字段偏移量
                        computeFieldOffsets();
                    } catch (InvalidClassException e) {
                        serializeEx = deserializeEx = new ExceptionInfo(e.classname, e.getMessage());
                        fields = NO_FIELDS;
                    }

                    // 实现了Externalizable接口
                    if (externalizable) {
                        // 获取无参构造方法
                        cons = getExternalizableConstructor(cl);
                    }
                    // 没有实现
                    else {
                        // 获取可序列化的构造方法???
                        cons = getSerializableConstructor(cl);
                        // 获取 private void writeObject(ObjectOutputStream)方法,必须非static
                        writeObjectMethod = getPrivateMethod(cl,
                                "writeObject",
                                new Class<?>[]{ObjectOutputStream.class},
                                Void.TYPE
                        );
                        // 获取private void readObject(ObjectInputStream)方法,必须非static
                        readObjectMethod = getPrivateMethod(cl,
                                "readObject",
                                new Class<?>[]{ObjectInputStream.class},
                                Void.TYPE
                        );
                        // private void readObjectNoData()方法,必须非static
                        readObjectNoDataMethod = getPrivateMethod(
                                cl,
                                "readObjectNoData",
                                null,
                                Void.TYPE
                        );
                        //
                        hasWriteObjectData = (writeObjectMethod != null);
                    }
                    // 可继承的Object writeReplace(),递归查找父类
                    writeReplaceMethod = getInheritableMethod(cl, "writeReplace", null, Object.class);
                    // 可继承的Object readResolve(),递归查找父类
                    readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
                    return null;
                }
            });
        }
        // 没有实现Serializable接口,则序列化id默认为0
        else {
            suid = 0L;
            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");
            }
        }
    }

2.2.对象替换机制

ObjectOutputStream支持在序列化之前,由用户替换要序列化的目标对象,需要在原始序列化类体系中,存在一个子类可见的writeReplace()方法.

在反序列化时,则通过readResolve()方法,将对象转换回来.

举例如下:

class User implements Serializable {
  
  private Object writeReplace() {
      return this; 
  }
  
  private Object readResolve() {
      return this; 
  }
}

或者可以这样写:

子类通过继承,同样存在writeReplace()/readResolve()方法.

class BaseEntity implements Serializable {
    protected Object writeReplace() {
      return this; 
  }
  
  protected Object readResolve() {
      return this; 
  }
}
class User extends BaseEntity {
  
}

2.3.特殊对象的序列化

2.3.1.ObjectStreamClass对象

    void writeNonProxy(ObjectOutputStream out) throws IOException {
        // 类名
        out.writeUTF(name);
        // 序列化id
        out.writeLong(getSerialVersionUID());

        byte flags = 0;
        // 实现了Externalizable接口
        if (externalizable) {
            flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
            int protocol = out.getProtocolVersion();
            // 块数据标记
            if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
                flags |= ObjectStreamConstants.SC_BLOCK_DATA;
            }
        }
        // 实现了Serializable接口
        else if (serializable) {
            flags |= ObjectStreamConstants.SC_SERIALIZABLE;
        }
        // 自主定义了writeObject()方法
        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());
            }
        }
    }

2.3.2.String对象

在序列化时,会把String对象进行UTF-8编码,获取字节序列,

之后先写入字节序列的长度,再写入字节序列本身.

当字节序列长度<=65535时,用short表示(占用2字节),否则用long表示(占用8字节).

2.3.3.数组对象

数组对象相比普通对象,需要多序列数组类自身的描述符,以及数组的长度.

    private void writeArray(Object array, ObjectStreamClass desc, boolean unshared) throws IOException {
        bout.writeByte(TC_ARRAY);
        // 写数组描述符
        writeClassDesc(desc, false);
        // 缓存引用
        handles.assign(unshared ? null : array);

        // 数组元素的类型
        Class<?> ccl = desc.forClass().getComponentType();
        // 基本类型
        if (ccl.isPrimitive()) {
            // int
            if (ccl == Integer.TYPE) {
                int[] ia = (int[]) array;
                // 数组长度
                bout.writeInt(ia.length);
                // 数组元素值
                bout.writeInts(ia, 0, ia.length);
            } else if (ccl == Byte.TYPE) {
                byte[] ba = (byte[]) array;
                bout.writeInt(ba.length);
                bout.write(ba, 0, ba.length, true);
            } else if (ccl == Long.TYPE) {
                long[] ja = (long[]) array;
                bout.writeInt(ja.length);
                bout.writeLongs(ja, 0, ja.length);
            } else if (ccl == Float.TYPE) {
                float[] fa = (float[]) array;
                bout.writeInt(fa.length);
                bout.writeFloats(fa, 0, fa.length);
            } else if (ccl == Double.TYPE) {
                double[] da = (double[]) array;
                bout.writeInt(da.length);
                bout.writeDoubles(da, 0, da.length);
            } else if (ccl == Short.TYPE) {
                short[] sa = (short[]) array;
                bout.writeInt(sa.length);
                bout.writeShorts(sa, 0, sa.length);
            } else if (ccl == Character.TYPE) {
                char[] ca = (char[]) array;
                bout.writeInt(ca.length);
                bout.writeChars(ca, 0, ca.length);
            } else if (ccl == Boolean.TYPE) {
                boolean[] za = (boolean[]) array;
                bout.writeInt(za.length);
                bout.writeBooleans(za, 0, za.length);
            } else {
                throw new InternalError();
            }
        }
        // 引用类型
        else {
            Object[] objs = (Object[]) array;
            int len = objs.length;
            // 数组长度
            bout.writeInt(len);
            if (extendedDebugInfo) {
                debugInfoStack.push(
                        "array (class \"" + array.getClass().getName() +
                                "\", size: " + len + ")");
            }
            try {
                for (int i = 0; i < len; i++) {
                    if (extendedDebugInfo) {
                        debugInfoStack.push("element of array (index: " + i + ")");
                    }
                    try {
                        writeObject0(objs[i], false);
                    } finally {
                        if (extendedDebugInfo) {
                            debugInfoStack.pop();
                        }
                    }
                }
            } finally {
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
        }
    }

2.3.4.枚举对象

枚举对象,需要首先序列化枚举类对应的ObjectStreamClass描述符,之后把枚举值的name按照String方式进行序列化.

    private void writeEnum(Enum<?> en, ObjectStreamClass desc, boolean unshared) throws IOException {
        bout.writeByte(TC_ENUM);
        // 获取枚举类的父类描述符
        ObjectStreamClass sdesc = desc.getSuperDesc();
        writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
        // 缓存对象
        handles.assign(unshared ? null : en);
        // 以枚举名称序列化
        writeString(en.name(), false);
    }

2.3.5.Class对象(JDK代理和普通类)

Class对象,把它对应的ObjectStreamClass描述符序列化即可.

但是,需要针对JDK的Proxy类生成的动态代理类进行特殊的序列化处理.

Proxy生成的动态代理类,只需要序列化其代理接口的全限定名称即可.

2.4.普通对象序列化

2.4.1.外部序列化(针对实例整体)

框架允许用户自主定义实例的序列化逻辑,只需要实例对应的类体系中,实现Externalizable接口即可.

比如,我们可以使用JSON格式进行对象的序列化,如下:

public class ExternalizableObject implements Externalizable {

    private String data;

    /**
     * 序列化使用的构造方法
     */
    public ExternalizableObject() {

    }

    public ExternalizableObject(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "ExternalizableObject{" +
                "data='" + data + '\'' +
                '}';
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        String json = JSON.toJSONString(this);
        out.writeUTF(json);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException {
        String json = in.readUTF();
        ExternalizableObject obj = JSON.parseObject(json, getClass());
        this.data = obj.data;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        PipedOutputStream out = new PipedOutputStream();
        PipedInputStream in = new PipedInputStream();
        out.connect(in);
        try (ObjectOutputStream objOut = new ObjectOutputStream(out)) {
            objOut.writeObject(new ExternalizableObject("你好"));
        }
        try (ObjectInputStream objIn = new ObjectInputStream(in)) {
            Object obj = objIn.readObject();
            System.out.println(obj);
        }
    }
}

2.4.2.内部序列化(针对某一类层级)

2.4.2.1.概述

如果只实现Serializable接口,而不实现Eternalizable接口,则框架会使用内部的序列化机制.

按照类的继承关系,由父类及子类,逐层进行序列化.

每层序列化时,优先识别类中定义的writeObject()方法,如果不存在,则通过反射识别要序列化的字段集合.

2.4.2.2.自定义当前层级的序列化

用户可通过定义如下格式的方法,自行实现当前层级的序列化逻辑.

注意:方法一定要是private的,因为这个方法不允许继承,框架会调用每一层的私有方法.

private void writeObject(ObjectOutputStream out);

private void readObject(ObjectInputStream in);

2.4.2.3.默认序列化机制

如果某一类层级没有定义writeObject()/readObject()方法,则框架会收集需要序列化的字段集合.

优先识别类中定义的serialPersistentFields字段,具体定时方式如下:

private static final ObjectStreamField[] serialPersistentFields;

如果没有声明该字段,则通过反射的方式,筛选当前类中声明的所有非static且非transient的字段,作为序列化字段集合.

字段列表是有序的,采用基本类型优先,其次按照字段名称字典序排列.

2.5.对象图中相同引用的处理

ObjectOutputStream中,有一个叫做处理表(HandleTable)的数据结构;
private final HandleTable handles;
HandleTable是一个简易的哈希表实现.

ObjectOutputStream每序列化一个对象之前,都通过HandleTable.lookup(obj)查找该对象是否已经被序列化过,如果找到则序列化该对象在objs数组中的索引位置,而不是序列化对象本身,

序列化之后,通过HandleTable.assign(obj)保存对象引用.

以这种方式避免相同引用的重复序列化,来保证反序列化时,维护正确的对象引用关系.

    private static class HandleTable {

        /**
         * 元素个数
         */
        private int size;
        /**
         * 阈值,达到阈值触发扩容
         */
        private int threshold;
        /**
         * 负载因子
         */
        private final float loadFactor;
        /**
         * 哈希表,默认-1填充
         * 元素值不是-1,则代表链表头元素对应的对象在objs数组中的索引编号
         */
        private int[] spine;
        /**
         * 对象的next指针,
         * next[i]代表objs[i]的后继元素是objs[next[i]]
         */
        private int[] next;
        /**
         * 保存所有对象引用
         */
        private Object[] objs;

        /**
         * 构造方法
         * 创建新的处理表使用给定的容量和负载因子
         *
         * @param initialCapacity 初始容量
         * @param loadFactor      负载因子
         */
        HandleTable(int initialCapacity, float loadFactor) {
            this.loadFactor = loadFactor;
            spine = new int[initialCapacity];
            next = new int[initialCapacity];
            objs = new Object[initialCapacity];
            threshold = (int) (initialCapacity * loadFactor);
            clear();
        }

        /**
         * 保存指定的对象引用
         * @param obj 指定的对象引用
         * @return
         */
        int assign(Object obj) {
            // 对象存储扩容
            if (size >= next.length) {
                growEntries();
            }
            // 哈希表扩容
            if (size >= threshold) {
                // spine扩容
                growSpine();
            }
            // 插入对象
            insert(obj, size);
            return size++;
        }

        /**
         * 查找指定的对象
         *
         * @param obj 指定的对象
         * @return 对象在objs中的位置索引
         */
        int lookup(Object obj) {
            if (size == 0) {
                return -1;
            }
            // hash slot
            int index = hash(obj) % spine.length;
            // 遍历链表,查找指定对象引用
            for (int i = spine[index]; i >= 0; i = next[i]) {
                // 引用相同,则返回位置索引
                if (objs[i] == obj) {
                    return i;
                }
            }
            return -1;
        }

        void clear() {
            Arrays.fill(spine, -1);
            Arrays.fill(objs, 0, size, null);
            size = 0;
        }

        int size() {
            return size;
        }

        private void insert(Object obj, int handle) {
            int index = hash(obj) % spine.length;
            objs[handle] = obj;
            next[handle] = spine[index];
            spine[index] = handle;
        }

        private void growSpine() {
            spine = new int[(spine.length << 1) + 1];
            threshold = (int) (spine.length * loadFactor);
            Arrays.fill(spine, -1);
            for (int i = 0; i < size; i++) {
                insert(objs[i], i);
            }
        }

        private void growEntries() {
            int newLength = (next.length << 1) + 1;
            int[] newNext = new int[newLength];
            System.arraycopy(next, 0, newNext, 0, size);
            next = newNext;

            Object[] newObjs = new Object[newLength];
            System.arraycopy(objs, 0, newObjs, 0, size);
            objs = newObjs;
        }

        private int hash(Object obj) {
            return System.identityHashCode(obj) & 0x7FFFFFFF;
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容