前言

在面试中,Java 序列化被问到的几率还是挺高的。所以搜集了 Java 序列化常见的问题,由浅入深的帮助大家进一步学习和理解。


序列化基础知识

什么是序列化?

Java 序列化是 JDK 1.1 中引入的特性之一。

总的来说,序列化讲一个 Java 对象所描述的所有内容以文件 IO 的方式 存储 或 传输 的过程。核心作用是对象状态的保存和重建。

在这里有两个比较重要的概念:

  • 序列化:把 Java 对象转换为字节码的过程
  • 反序列化:把字节码还原为 Java 对象的过程

为什么要序列化 ?

因为 Java 对象是存放在 JVM 的 堆内存 中的,当 JVM 退出的时候,对象也就随之销毁。如果想 持久化 或进行 网络传输 对象数据时,那就必须把对象转为计算机可以识别的字节码。

在以下场景中需要使用到序列化。

  • 持久化数据:文件、数据库、缓存
  • 网络传输:RMI (远程调用 Remote Method Invocation)、RPC

如何实现序列化

在 Java 中,没有关键字可以直接去定义一个所谓的 可持久化 对象。这就需要我们在代码中 显示地 进行序列化和反序列化还原操作。

Serializable 接口

Serializable 接口是一个 标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。

1、定义


2、序列化


3、反序列化


4、结果


5、如果不实现 Serializable 接口将无法进行序列化或反序列化


Externalizable 接口

Externalizable 继承了 Serializable 接口,还定义了两个抽象方法:writeExternal() 和 readExternal()。

如果开发人员使用 Externalizable 来实现序列化和反序列化,必须重写 writeExternal() 和 readExternal() 方法。

因为实现 Externalizable 接口之后,基于 Serializable 接口的默认化序列化机制就会失效。


Serializable 和 Externalizable 的区别

Serializable Externalizable
Java 支持比较完整,自动存储必要信息 需要开发人员自己完成
所有对象由 Java 统一保存,性能较低 开发人员决定哪个对象保存,可以提升速度
保存时占用空间大,性能差 部分存储,空间占用可能较少,性能相对高

Java 序列化协议分析

下面这段字节码是保存在本地的字节码文件,接下来准备对这段字节码进行 拆分 和 讲解 (只针对 Serializable)。

以下的字节码定义参考 java.io.ObjectStreamConstants 中的定义,如果有兴趣,找到这个类,里面有详细的定义。


  • JDk 序列化的魔数
  • aced STREAM_MAGIC 魔数,用于标识当前文件的头部
  • 0005 STREAM_VERSION 序列化协议版本号
  • 描述对象的类型信息
  • 73 TC_OBJECT 表示序列化的是一个普通 Java 对象 (Object 0x73,String 0x74,Array 0x75)
  • 72 TC_CLASSDESC 表示当前的对象的类型信息
  • 0014 表示类名的长度,这段代码中是 0014 换算过来是 20 个字节
  • 7374 6174 6963 4661 6374 6f72 792e 5065 7273 6f6e 表示类名,即 staticFactory.Person
  • 0000 0000 0000 0001 类名后的 8 个字节是一个长整数,即 serialVersionUID = 1L
  • 02 SC_SERIALIZABLE 标识位,说明这个类实现了 Serializable 接口。
  • 对象的字段表
  • 0002 表示这个对象中有 2 个属性
  • 49 即 I 表示 int,说明这是一个 32 位整数
  • 0003 表示属性名的长度,即 3 字节
  • 6167 65 表示属性 age
  • 4c 即 L,表示引用类型,说明这个属性是某个类型的引用
  • 0004 表示属性名的长度,即 4 字节
  • 6e61 6d65 表示属性 name
  • 74 TC_STRING 表示后面是个字符串
  • 0012 表示字符串长度,即 18 字节
  • 4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b 即 Ljava/lang/String
  • 父类的描述信息
  • 78 TC_ENDBLOCKDATA 标志所有的字段类型信息描述结束
  • 70 TC_NULL 代表 null,即没有父类
  • 对象的属性值
  • 0000 001e 初始化后的年龄,转换后即 30
  • 74 TC_STRING 表示后面是个字符串
  • 0005 表示字符串长度为 5
  • 4865 6e72 79 初始化之后的姓名,转换后即 Henry

序列化的特性

在实际应用中,有些时候 不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据。

本段重点讨论 transient 和 static 之间的区别,并讨论每个关键字的作用。

transient 关键字

当我们的一个字段被声明为 transient 后,默认序列化机制就会忽略掉该字段的内容,不会被保存。

static 关键字

序列化仅对特定的变量产生作用,但 static 修饰的变量并不特定于任何对象。因此,静态变量不会参与序列化。

虽然用关键字可以避免序列化,但是当关键字组合使用的时候,也可能会失效。

transient 和 static 的规则

  1. 临时变量在序列化过程中将被忽略。
  2. static 变量不会参与序列化。
  3. 如果在声明本身期间对值进行了初始化,则静态变量将被序列化。
  4. 如果一个变量同时包含 transient 和 static 关键字,并且该值在声明期间被初始化,则它将被序列化。因为在这里 transient 修饰符会被忽略,而 static 修饰符将执行操作。
  5. final 变量将被序列化。
  6. 如果一个变量同时包含 final 和 transient 关键字,那么它就会被序列化。因为在这里 transient 修饰符会被忽略,而 final 修饰符将执行操作。

下面用一段代码验证一些。

1、定义一个实例化类


2、序列化


3、反序列化


4、输出结果


重点:

  • One 和 Two 为 null,根据规则 1,使用 tresient 修饰的变量不参与序列化
  • Three 为 null,根据规则 2, static 变量不参与序列化
  • Four 之所以为 V4,根据规则 3,仅在声明期间初始化该值,静态变量才会被序列化
  • Five 为 null,根据规则4,因为它被 static 和 tresient 同时修饰,并且值在生命期间未初始化
  • Six 之所以为 6,根据规则 4,如果同时 static 和 tresient 同时修饰,并且该值在声明期间已初始化,那就会被序列化
  • Seven 是 V7,根据规则 5,用 final 修饰的会被序列化
  • Eight 之所以为 V8,根据规则 6,如果变量同时被 final 和 tresient 修饰,那就会被序列化

serialVersionUID 具体作用是什么?

在序列化中,还有一个特别重要的步骤,需要指定 serialVersionUID 版本号。

如果反序列化使用的 Class 的版本号与序列化时候使用的不一致,则会报异常。

序列化版本号可以随意的指定。

如果不指定,JVM 会 自己计算 一个版本号,但随着 Class 的升级,就无法正确反序列化。

不指定版本号还有另一个明显隐患,不利于 JVM 间的移植,可能 Class 文件没有更改,但不同 JVM 可能计算的规则不一样,这样也会导致无法反序列化。

Java 序列化的缺陷

无法跨平台

现在的系统设计越来越多元化,项目里可能会用多种语言来编写应用程序,比如 Java、C++、Python 同时配合使用。

而 Java 序列化只适用于基于 Java 语言实现的框架。其他语言大部分没有使用 Java 的序列化框架。如果两个基于不同语言编写的应用程序相互通信,那么久无法实现两个应用服务之间的序列化与反序列化。

容易被攻击

对象是通过在 ObjectInputStream 上调用 readObject() 方法进行反序列化的,它可以将类路径上几乎所有实现了 Serializable 接口的对象都实例化。这意味着,在反序列化字节流的过程中,该方法可以 执行任意类型的代码,这是非常危险的。

对于需要长时间进行反序列化的对象,不需要执行任何代码,也可以发起一次攻击。攻击者可以创建循环对象链,然后将序列化后的对象传输到程序中反序列化,这种情况会导致 hashCode 方法被调用次数呈次方爆发式增长, 从而引发栈溢出异常。

序列化后的流太大

序列化后的二进制流大小能体现序列化的性能。序列化后的二进制数组越大,占用的存储空间就越多,存储硬件的成本就越高。如果我们是进行网络传输,则占用的带宽就更多,这时就会影响到系统的吞吐量。

序列化的性能太差

Java 的序列化耗时比较大。序列化的速度也是体现序列化性能的重要指标,如果序列化的速度慢,就会影响网络通信的效率,从而增加系统的响应时间。

序列化的其它问题

单例模式与序列化

首先抛出一个问题,单例模式真的能够实现实例的唯一性吗?

答案是否定的,很多人都知道反射可以 恶意破坏单例模式。其实除了反射以外,使用序列化与反序列化也同样会破坏掉单例。比如下面这个单例:


上边这种情况,其实已经破坏掉单例。因为序列化会通过反射调用无参构造器返回一个新的对象,从而破坏了单例模式,解决办法就是 添加 readResolve() 方法,返回指定的对象。

巨人的肩膀

  • 刘超 网络通信优化之序列化:避免使用 Java 序列化 「极客时间」
  • Java 优化

再来认识一下 Java 序列化的更多相关文章

  1. (记录)Jedis存放对象和读取对象--Java序列化与反序列化

    一.理论分析 在学习Redis中的Jedis这一部分的时候,要使用到Protostuff(Protobuf的Java客户端)这一序列化工具.一开始看到序列化这些字眼的时候,感觉到一头雾水.于是,参考了 ...

  2. Java 序列化与反序列化

    1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频. ...

  3. Java序列化与反序列化

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  4. java序列化

    什么是java序列化,如何实现java序列化? 我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如,要将java对象存储到硬盘或者传送给网络上的其他计算机 ...

  5. Java 序列化Serializable详解

    Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...

  6. 关于java序列化中的一个细节

    java序列化机制的可以参考很多资料了,最近在看的时候发现了一些问题. 1. 默认的序列化机制,很多书里讲到序列化类只序列化类名,实例变量,不会实例化类变量(static)和瞬态变量(transien ...

  7. 各种Java序列化性能比较

    转载:http://www.jdon.com/concurrent/serialization.html 这里比较Java对象序列化 XML JSON  Kryo  POF等序列化性能比较. 很多人以 ...

  8. Java序列化格式详解

    RPC的世界,由于涉及到进程间网络远程通信,不可避免的需要将信息序列化后在网络间传送,序列化有两大流派: 文本和二进制. 文本序列化 序列化的实现有很多方式,在异构系统中最常用的就是定义成人类可读的文 ...

  9. 简述java序列化

      1. 什么是Java对象序列化     Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期 ...

随机推荐

  1. Eclipse无法打开提示could not open jvm.cfg错误

    先言:我的问题只是出在之前我的java_jdk原来安装在F:\Temp这里,但是我之后在整理文件的时候把这个文件夹改成了F:\java_jdk.所以导致了错误的出现 解决方法1: 因为我的只是目录名称 ...

  2. 自动化将 word 转为 pdf,再将pdf转为图片!

    参考: https://blog.csdn.net/ynyn2013/article/details/49120731 https://www.jianshu.com/p/f57cc64b9f5e 一 ...

  3. Netty (一) IO 基础篇

    Java IO 演进之路   1.1 必须明白的几个概念 1.1.1 阻塞(Block)和非阻塞(Non-Block) 阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准 ...

  4. git branch All In One

    git branch All In One Git Branch Management https://git-scm.com/book/en/v2/Git-Branching-Branch-Mana ...

  5. 如何使用 js 实现一个 debounce 函数

    如何使用 js 实现一个 debounce 函数 原理 防抖: 是指在指定的单位时间内,如果重复触发了相同的事件,则取消上一次的事件,重新开始计时! 实现方式 "use strict&quo ...

  6. MDN Browser Compatibility Report 2020

    MDN Browser Compatibility Report 2020 top pain point https://mdn-web-dna.s3-us-west-2.amazonaws.com/ ...

  7. TypeScript & Object Error

    TypeScript & Object Error Element implicitly has an 'any' type because expression of type 'any' ...

  8. flutter 使用Android studio编辑kt插件

    使用android studio打开/example/android 文件即可

  9. Dart http库

    推荐下我写的一个http库ajanuw_http 最基本的获取数据 import 'package:http/http.dart' as http; main(List<String> a ...

  10. BGV暴涨千倍,未来或将超越YFI领跑DeFi全场!

    毫无疑问,YFI在2020年上半年以一己之力掀翻了DeFi市场的热潮.迄今为止,YFI的新鲜资讯从不缺席,最近也是频频登上各大知名媒体热搜.其币价远远超过比特币价格,也让资本市场注意到DeFi市场原来 ...