[转帖]Java IO篇:序列化与反序列化

java,io,序列化 · 浏览次数 : 0

小编点评

**4.1、易被攻击:** - 反序列化漏洞:攻击者可以利用反序列化过程构造恶意代码,使得程序在反序列化的过程中执行任意代码。 - NIO 中的 ByteBuffer 编码:序列化的速度太慢,网络通信效率低,从而增加系统的响应时间。 **4.2、序列化性能太差:** - ObjectOutputStream 序列化时间:29 - ByteBuffer 序列化时间:6 **4.3、序列化性能太差:** - ObjectOutputStream 序列化时间:29 - ByteBuffer 序列化时间:6 **4.4、 NIO 中的 ByteBuffer 编码性能比较高:** - ByteBuffer 编码完成的数组大小等于 ObjectOutputStream 的字节编码长度,从而提高了效率。

正文

1、什么是序列化:

        两个服务之间要传输一个数据对象,就需要将对象转换成二进制流,通过网络传输到对方服务,再转换成对象,供服务方法调用。这个编码和解码的过程称之为序列化和反序列化。所以序列化就是把 Java 对象变成二进制形式,本质上就是一个byte[]数组。将对象序列化之后,就可以写入磁盘进行保存或者通过网络中输出给远程服务了。反之,反序列化可以从网络或者磁盘中读取的字节数组,反序列化成对象,在程序中使用。

2、序列化优点:

① 永久性保存对象:将对象转为字节流存储到硬盘上,即使 JVM 停机,字节流还会在硬盘上等待,等待下一次 JVM 启动时,反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间

② 方便网络传输:序列化成字节流形式的对象可以方便网络传输(二进制形式),节约网络带宽

③ 通过序列化可以在进程间传递对象

3、序列化的几种方式:

参考文章:https://www.jianshu.com/p/7298f0c559dc

3.1、Java 原生序列化:

        Java 默认通过 Serializable 接口实现序列化,只要实现了该接口,该类就会自动实现序列化与反序列化,该接口没有任何方法,只起标识作用。Java序列化保留了对象类的元数据(如类、成员变量、继承类信息等),以及对象数据等,兼容性最好,但不支持跨语言,而且性能一般。

        实现 Serializable 接口的类在每次运行时,编译器会根据类的内部实现,包括类名、接口名、方法和属性等自动生成一个 serialVersionUID,serialVersionUID 主要用于验证对象在反序列化过程中,序列化对象是否加载了与序列化兼容的类,如果是具有相同类名的不同版本号的类,在反序列化中是无法获取对象的,显式地定义 serialVersionUID 有两种用途:

  • 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的 serialVersionUID;
  • 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的 serialVersionUID;

如果源码改变,那么重新编译后的 serialVersionUID 可能会发生变化,因此建议一定要显示定义 serialVersionUID 的属性值。

3.2、Hessian 序列化:

        Hessian 序列化是一种支持动态类型、跨语言、基于对象传输的网络协议。Java 对象序列化的二进制流可以被其他语言反序列化。 Hessian 协议具有如下特性:

  • 自描述序列化类型。不依赖外部描述文件或接口定义, 用一个字节表示常用
  • 基础类型,极大缩短二进制流
  • 语言无关,支持脚本语言
  • 协议简单,比 Java 原生序列化高效

        Hessian 2.0 中序列化二进制流大小是 Java 序列化的 50%,序列化耗时是 Java 序列化的 30%,反序列化耗时是 Java 反序列化的20% 。

        Hessian 会把复杂对象所有属性存储在一个 Map 中进行序列化。所以在父类、子类存在同名成员变量的情况下, Hessian 序列化时,先序列化子类 ,然后序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。

3.3、Json 序列化:

        JSON 是一种轻量级的数据交换格式。JSON 序列化就是将数据对象转换为 JSON 字符串,在序列化过程中抛弃了类型信息,所以反序列化时需要提供类型信息才能准确地反序列化,相比前两种方式,JSON 可读性比较好,方便调试。

4、为什么不建议使用Java序列化

该部分主要参考文章:为什么我不建议你使用Java序列化 - 掘金

        目前主流框架很少使用到 Java 序列化,比如 SpringCloud 使用的 Json 序列化,Dubbo 虽然兼容 Java 序列化,但默认使用的是 Hessian 序列化。这是为什么呢?主要是因为 JDK 默认的序列化方式存在以下一些缺陷:无法跨语言、易被攻击、序列化的流太大、序列化性能太差等

4.1、无法跨语言:

        Java 序列化只支持 Java 语言实现的框架,其它语言大部分都没有使用 Java 的序列化框架,也没有实现 Java 序列化这套协议,因此,两个不同语言编写的应用程序之间通信,无法使用 Java 序列化实现应用服务间传输对象的序列化和反序列化。

4.2、易被攻击:

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

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

        序列化通常会通过网络传输对象,而对象中往往有敏感数据,所以序列化常常成为黑客的攻击点,攻击者巧妙地利用反序列化过程构造恶意代码,使得程序在反序列化的过程中执行任意代码。 Java 工程中广泛使用的 Apache Commons Collections、Jackson、fastjson 等都出现过反序列化漏洞。如何防范这种黑客攻击呢?有些对象的敏感属性不需要进行序列化传输,可以加 transient 关键字,避免把此属性信息转化为序列化的二进制流,除此之外,声明为 static 类型的成员变量也不能要序列化。如果一定要传递对象的敏感属性,可以使用对称与非对称加密方式独立传输,再使用某个方法把属性还原到对象中。

4.3、序列化后的流太大

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

        Java 序列化中使用了 ObjectOutputStream 来实现对象转二进制编码,那么这种序列化机制实现的二进制编码完成的二进制数组大小,相比于 NIO 中的 ByteBuffer 实现的二进制编码完成的数组大小,有没有区别呢?

我们可以通过一个简单的例子来验证下:

  1. User user = new User();
  2. user.setUserName("test");
  3. user.setPassword("test");
  4. ByteArrayOutputStream os =new ByteArrayOutputStream();
  5. ObjectOutputStream out = new ObjectOutputStream(os);
  6. out.writeObject(user);
  7. byte[] testByte = os.toByteArray();
  8. System.out.print("ObjectOutputStream 字节编码长度:" + testByte.length + "\n");
  1. ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
  2. byte[] userName = user.getUserName().getBytes();
  3. byte[] password = user.getPassword().getBytes();
  4. byteBuffer.putInt(userName.length);
  5. byteBuffer.put(userName);
  6. byteBuffer.putInt(password.length);
  7. byteBuffer.put(password);
  8. byteBuffer.flip();
  9. byte[] bytes = new byte[byteBuffer.remaining()];
  10. System.out.print("ByteBuffer 字节编码长度:" + bytes.length+ "\n");

运行结果:

ObjectOutputStream 字节编码长度:99
ByteBuffer 字节编码长度:16

 4.4、序列化性能太差:

        序列化的速度也是体现序列化性能的重要指标,如果序列化的速度慢,网络通信效率就低,从而增加系统的响应时间。我们再来通过上面这个例子,来对比下 Java 序列化与 NIO 中的 ByteBuffer 编码的性能:

  1. User user = new User();
  2. user.setUserName("test");
  3. user.setPassword("test");
  4. long startTime = System.currentTimeMillis();
  5. for(int i=0; i<1000; i++) {
  6. ByteArrayOutputStream os =new ByteArrayOutputStream();
  7. ObjectOutputStream out = new ObjectOutputStream(os);
  8. out.writeObject(user);
  9. out.flush();
  10. out.close();
  11. byte[] testByte = os.toByteArray();
  12. os.close();
  13. }
  14. long endTime = System.currentTimeMillis();
  15. System.out.print("ObjectOutputStream 序列化时间:" + (endTime - startTime) + "\n");
  1. long startTime1 = System.currentTimeMillis();
  2. for(int i=0; i<1000; i++) {
  3. ByteBuffer byteBuffer = ByteBuffer.allocate( 2048);
  4. byte[] userName = user.getUserName().getBytes();
  5. byte[] password = user.getPassword().getBytes();
  6. byteBuffer.putInt(userName.length);
  7. byteBuffer.put(userName);
  8. byteBuffer.putInt(password.length);
  9. byteBuffer.put(password);
  10. byteBuffer.flip();
  11. byte[] bytes = new byte[byteBuffer.remaining()];
  12. }
  13. long endTime1 = System.currentTimeMillis();
  14. System.out.print("ByteBuffer 序列化时间:" + (endTime1 - startTime1)+ "\n");

运行结果:

ObjectOutputStream 序列化时间:29
ByteBuffer 序列化时间:6

文章知识点与官方知识档案匹配,可进一步学习相关知识
Java技能树IO流概述97422 人正在系统学习中

与[转帖]Java IO篇:序列化与反序列化相似的内容:

[转帖]Java IO篇:序列化与反序列化

1、什么是序列化: 两个服务之间要传输一个数据对象,就需要将对象转换成二进制流,通过网络传输到对方服务,再转换成对象,供服务方法调用。这个编码和解码的过程称之为序列化和反序列化。所以序列化就是把 Java 对象变成二进制形式,本质上就是一个byte[]数组。将对象序列化之后,就可以写入磁盘进行保存或

[转帖]Java IO篇:序列化与反序列化

1、什么是序列化: 两个服务之间要传输一个数据对象,就需要将对象转换成二进制流,通过网络传输到对方服务,再转换成对象,供服务方法调用。这个编码和解码的过程称之为序列化和反序列化。所以序列化就是把 Java 对象变成二进制形式,本质上就是一个byte[]数组。将对象序列化之后,就可以写入磁盘进行保存或

[转帖]Java IO篇:什么是 Reactor 网络模型?

一、什么是 Reactor 模型: The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by

[转帖]Java IO篇:什么是零拷贝?

在介绍零拷贝的IO模式之前,我们先简单了解下传统的IO模式是怎么样的? 一、传统的IO模式: 传统的IO模式,主要包括 read 和 write 过程: read:把数据从磁盘读取到内核缓冲区,再拷贝到用户缓冲区write:先把数据写入到 socket缓冲区,最后写入网卡设备 流程图如下: (1)用

[转帖]Java IO篇:什么是 Reactor 网络模型?

一、什么是 Reactor 模型: The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by

[转帖]解决Java中的java.io.IOException: Broken pipe问题

https://www.cnblogs.com/Chary/p/16835248.html Java 中java.io.IOException: Broken pipe 认识broken pipe pipe是管道的意思,管道里面是数据流,通常是从文件或网络套接字读取的数据。 当该管道从另一端突然关闭

[转帖]JAVA 对象序列化

https://cloud.tencent.com/developer/news/276874 文章来源:企鹅号 - 燃照爱宠物 所谓的『JAVA对象序列化』就是指,将一个JAVA对象所描述的所有内容以文件IO的方式写入二进制文件的一个过程。关于序列化,主要涉及两个流,ObjectInputStre

[转帖]JVM-工具-jcmd

http://events.jianshu.io/p/011f0e3a39ff 一、jcmd 用法 1.1 基本知识 jcmd 是在 JDK1.7 以后,新增了一个命令行工具。 jcmd 是一个多功能的工具,相比 jstat 功能更为全面的工具,可用于获取目标 Java 进程的性能统计、JFR、内存

[转帖]我所知道的线程池

https://bigbully.github.io/%E7%BA%BF%E7%A8%8B%E6%B1%A0 线程池其实或多或少都用过,不过这是我第一次阅读它的源码,包括源码附带的非常详尽的注释。发现我之前对于线程池的理解还是很浅薄的。 其实从ThreadPoolExecutor.java顶部200

【转帖】47.直接内存

目录 1.直接内存概述2.`IO`与`NIO`对比3.直接内存的`OOM`与内存大小设置 1.直接内存概述 1.直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。 2.直接内存是在Java堆外,直接向系统申请的内存空间 3.Java的NIO库允许使用直接内存,用于数据