前言
最近在项目中使用ObjectInputStream/ObjectOutputStream进行对象的序列化和反序列化,出现了OOM的问题,在解决的过程中简单的研究了一下对象的序列化和反序列化(使用Serializable接口)的过程,简单做一个记录。发现了一个持久化存储序列化数据的安全风险,可能会受到恶意攻击,导致必现的OOM。
使用场景
1 数据使用方案
持久化过程:应用在使用过程中,首先使用ObjectOutputStream的writeObject接口将对象序列化成byte数据,然后利用加密算法对序列化数据进行加密,最终将加密后的数据持久化存储到应用的数据目录下的某个文件中。
读取解析过程:首先将数据从文件中读取出来,然后用对应的解密算法解密,最后使用对应的ObjectInputStream的readObject接口将字节流解析成对应的对象。
2 遇到的问题
上述方案在使用的过程中,遇到以下两种OOM的崩溃
(1) OOM 1
java.lang.OutOfMemoryError: Failed to allocate a 942137073 byte allocation with 4194240 free bytes and 487MB until OOM
at java.io.ObjectInputStream.readBlockDataLong(ObjectInputStream.java:569)
at java.io.ObjectInputStream.readContent(ObjectInputStream.java:699)
at java.io.ObjectInputStream.discardData(ObjectInputStream.java:636)
at java.io.ObjectInputStream.readNewClassDesc(ObjectInputStream.java:1662)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:657)
at java.io.ObjectInputStream.readNewObject(ObjectInputStream.java:1782)
at java.io.ObjectInputStream.readNonPrimitiveContent(ObjectInputStream.java:761)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1983)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1940)
(2) OOM 2
java.lang.OutOfMemoryError: Failed to allocate a 789137073 byte allocation with 2317152 free bytes and 456MB until OOM
at java.io.DataInputStream.decodeUTF
at java.io.DataInputStream.decodeUTF
at java.io.ObjectInputStream.readContent(ObjectInputStream.java:699)
at java.io.ObjectInputStream.discardData(ObjectInputStream.java:636)
at java.io.ObjectInputStream.readNewClassDesc(ObjectInputStream.java:1662)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:657)
at java.io.ObjectInputStream.readNewObject(ObjectInputStream.java:1782)
at java.io.ObjectInputStream.readNonPrimitiveContent(ObjectInputStream.java:761)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1983)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1940)
堆栈里面大致的意思是,在用ObjectInputStream的readObject接口进行对象的反序列化的时候,需要分配900M+/700M+的内存,导致上层出现OOM,众所周知,应用java层能够分配的最大内存由系统属性dalvik.vm.heapsize定义,这个值根据不同的厂商和机器都有可能是不一样的,我手上的测试机配如下:
该机器的heapsize设置为256M,也就是该机器的每个应用虚拟机能够分配的最大内存即为256M,当虚拟机需要的内存超过256M时,会出现OutOfMemoryError的问题,这边顺便记录一下,很多人用Exception去捕获所有的异常,但是这样并不能捕获OutOfMemoryError,看一下继承关系:
由继承关系可知,OutOfMemoryError是继承自Error,和Exception并不是一个继承分支,因此想要捕获包括Error在内的所有异常,必须使用Throwable去捕获。
3 分析问题
3.1 堆栈分析
上述两个OOM实际上出现的原因是一样的,下面使用OOM1来着重分析这个问题,也就是最终调用ObjectInputStream.readBlockDataLong出现的OOM问题,先看一下这个函数: