上述方案在使用的过程中,遇到以下两种OOM的崩溃
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)
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)
/**
* Reads and returns an array of raw bytes with primitive data. The array
* will have up to 255 bytes. The primitive data will be in the format
* described by {@code DataOutputStream}.
*
* @return The primitive data read, as raw bytes
*
* @throws IOException
* If an IO exception happened when reading the primitive data.
*/
private byte[] readBlockData() throws IOException {
byte[] result = new byte[input.readByte() & 0xff];
input.readFully(result);
return result;
}
/**
* Reads and returns an array of raw bytes with primitive data. The array
* will have more than 255 bytes. The primitive data will be in the format
* described by {@code DataOutputStream}.
*
* @return The primitive data read, as raw bytes
*
* @throws IOException
* If an IO exception happened when reading the primitive data.
*/
private byte[] readBlockDataLong() throws IOException {
byte[] result = new byte[input.readInt()];
input.readFully(result);
return result;
}
上面贴出来了两个函数,readBlockData和readBlockDataLong函数,从函数名称分析,这两个函数的功能应该是类似的,readBlockDataLong函数像是用于读取较大数据量的数据,看一下注释,readBlockData函数用于读取数据量小于等于255的数据块,readBlockDataLong函数用于读取数据量大于255的数据块。
/**
* Reads the content of the receiver based on the previously read token
* {@code tc}.
*
* @param tc
* The token code for the next item in the stream
* @return the object read from the stream
*
* @throws IOException
* If an IO exception happened when reading the class
* descriptor.
* @throws ClassNotFoundException
* If the class corresponding to the object being read could not
* be found.
*/
private Object readContent(byte tc) throws ClassNotFoundException,
IOException {
switch (tc) {
case TC_BLOCKDATA:
return readBlockData();
case TC_BLOCKDATALONG:
return readBlockDataLong();
case TC_CLASSDESC:
return readNewClassDesc(false);
case TC_OBJECT:
return readNewObject(false);
case TC_LONGSTRING:
return readNewLongString(false);
case TC_EXCEPTION:
Exception exc = readException();
throw new WriteAbortedException("Read an exception", exc);
case TC_RESET:
resetState();
return null;
default:
throw corruptStream(tc);
}
}
/**
* Reads a new class descriptor from the receiver. It is assumed the class
* descriptor has not been read yet (not a cyclic reference). Return the
* class descriptor read.
*
* @param unshared
* read the object unshared
* @return The {@code ObjectStreamClass} read from the stream.
*
* @throws IOException
* If an IO exception happened when reading the class
* descriptor.
* @throws ClassNotFoundException
* If a class for one of the objects could not be found
*/
private ObjectStreamClass readNewClassDesc(boolean unshared)
throws ClassNotFoundException, IOException {
ObjectStreamClass newClassDesc = readClassDescriptor();
registerObjectRead(newClassDesc, descriptorHandle, unshared);
descriptorHandle = oldHandle;
primitiveData = emptyStream;
//load class...
// Consume unread class annotation data and TC_ENDBLOCKDATA
discardData();
checkedSetSuperClassDesc(newClassDesc, readClassDesc());
return newClassDesc;
}
/**
* Reads and discards block data and objects until TC_ENDBLOCKDATA is found.
*
* @throws IOException
* If an IO exception happened when reading the optional class
* annotation.
* @throws ClassNotFoundException
* If the class corresponding to the class descriptor could not
* be found.
*/
private void discardData() throws ClassNotFoundException, IOException {
primitiveData = emptyStream;
boolean resolve = mustResolve;
mustResolve = false;
do {
byte tc = nextTC();
if (tc == TC_ENDBLOCKDATA) {
mustResolve = resolve;
return; // End of annotation
}
readContent(tc);
} while (true);
}
看一下ObjectInputStream.readNewClassDesc函数注释,结合相关的代码,大概可以知道该函数的主要功能是读取序列化数据中class的描述,并用classloader将对应的class加载上来,然后调用discardData函数,看一下这个函数调用上面的注释,读取和消费不需要的数据,可能是一些注解annotation数据,直到读到TC_ENDBLOCKDATA为止。看一下TC_ENDBLOCKDATA的定义:
/**
* Tag to mark a long block of data. The long following this tag
* indicates the size of the block.
*/
public static final byte TC_BLOCKDATALONG = (byte) 0x7A;
这个tc代表的后面的数据块将是一个较大的数据块,tc后面的int型数据(4个字节组成)代表的是这个数据块的数据长度。
/**
* Writes buffered data to the target stream. This is similar to {@code
* flush} but the flush is not propagated to the target stream.
*
* @throws IOException
* if an error occurs while writing to the target stream.
*/
protected void drain() throws IOException {
if (primitiveTypes == null || primitiveTypesBuffer == null) {
return;
}
// If we got here we have a Stream previously created
int offset = 0;
byte[] written = primitiveTypesBuffer.toByteArray();
// Normalize the primitive data
while (offset < written.length) {
int toWrite = written.length - offset > 1024 ? 1024
: written.length - offset;
if (toWrite < 256) {
output.writeByte(TC_BLOCKDATA);
output.writeByte((byte) toWrite);
} else {
output.writeByte(TC_BLOCKDATALONG);
output.writeInt(toWrite);
}
// write primitive types we had and the marker of end-of-buffer
output.write(written, offset, toWrite);
offset += toWrite;
}
// and now we're clean to a state where we can write an object
primitiveTypes = null;
primitiveTypesBuffer = null;
}
/**
* Write a class descriptor {@code classDesc} (an
* {@code ObjectStreamClass}) to the stream.
*
* @param classDesc
* The class descriptor (an {@code ObjectStreamClass}) to
* be dumped
* @param unshared
* Write the object unshared
* @return the handle assigned to the class descriptor
*
* @throws IOException
* If an IO exception happened when writing the class
* descriptor.
*/
private int writeClassDesc(ObjectStreamClass classDesc, boolean unshared) throws IOException {
if (classDesc == null) {
writeNull();
return -1;
}
output.writeByte(TC_CLASSDESC);
writeClassDescriptor(classDesc);
annotateClass(classToWrite);
drain(); // flush primitive types in the annotation
output.writeByte(TC_ENDBLOCKDATA);
writeClassDesc(classDesc.getSuperclass(), unshared);
return handle;
}
/**
* Writes optional information for class {@code aClass} to the output
* stream. This optional data can be read when deserializing the class
* descriptor (ObjectStreamClass) for this class from an input stream. By
* default, no extra data is saved.
*
* @param aClass
* the class to annotate.
* @throws IOException
* if an error occurs while writing to the target stream.
* @see ObjectInputStream#resolveClass(ObjectStreamClass)
*/
protected void annotateClass(Class> aClass) throws IOException {
// By default no extra info is saved. Subclasses can override
}
import android.util.Log;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
public class AnObjectOutputStream extends ObjectOutputStream {
private static final String TAG = "AnObjectOutputStream";
/**
* 复现堆栈java.io.ObjectInputStream.readBlockDataLong
* 默认复现这个堆栈
*/
private static byte[] DISCARD_BYTES_LONG_DATA = new byte[] {
0x7a, 0x7a, 0x7a, 0x67, 0x67
};
/**
* 复现堆栈 java.io.DataInputStream.decodeUTF
* java.io.DataInputStream.decodeUTF
* java.io.ObjectInputStream.readNewLongString
*/
private static byte[] DISCARD_BYTES_LONG_STRING = new byte[] {
0x7c, 0x7a, 0x7a, 0x67, 0x67
};
private DataOutputStream mInnerOutput;
private boolean mStackBlockData = true;
public AnObjectOutputStream(OutputStream input) throws IOException {
super(input);
}
/**
* 调用setStackBlockData(false),将复现下面的堆栈
* 复现堆栈 java.io.DataInputStream.decodeUTF
* java.io.DataInputStream.decodeUTF
* java.io.ObjectInputStream.readNewLongString
*/
public void setStackBlockData(boolean blockData) {
mStackBlockData = blockData;
}
protected void annotateClass(Class> aClass) throws IOException {
// By default no extra info is saved. Subclasses can override
Log.i(TAG, "annotateClass aClass:" + aClass);
installOutputStream();
if (mInnerOutput == null) {
return;
}
if (mStackBlockData) {
mInnerOutput.write(DISCARD_BYTES_LONG_DATA);
} else {
mInnerOutput.write(DISCARD_BYTES_LONG_STRING);
}
Log.i(TAG, "annotateClass write success");
}
private void installOutputStream() {
Object obj = null;
try {
Field field = getClass().getSuperclass().getDeclaredField("output");
field.setAccessible(true);
obj = field.get(this);
} catch (Exception e) {
e.printStackTrace();
}
if (obj == null) {
Log.i(TAG, "installOutputStream failed");
return;
}
mInnerOutput = (DataOutputStream)obj;
}
}
由于ObjectOutputStream中的output成员属性为private,因此需要借助反射。果然,使用AnObjectOutputStream替代常规的ObjectOutputStream,运行一下必现的OOM,完整的调用如下:
import com.example.testpopupwindow.stream.AnObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializeThread extends Thread {
private static final String TAG = "SerializeThread";
private Employee mEmployee;
public void run() {
mEmployee = Employee.create("test");
Object obj = null;
try {
byte[] serializeRes = serialize();
obj = unserialize(serializeRes);
} catch (IOException e) {
e.printStackTrace();
}
}
private byte[] serialize() throws IOException {
ByteArrayOutputStream arrOs = new ByteArrayOutputStream();
ObjectOutputStream oos = new AnObjectOutputStream(arrOs);
oos.writeObject(mEmployee);
oos.flush();
byte[] outArr = arrOs.toByteArray();
oos.close();
return outArr;
}
private Object unserialize(byte[] serializedata) throws IOException {
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
byteArrayInputStream = new ByteArrayInputStream(serializedata);
objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
} catch (Exception e) {
}
return null;
}
/**
* test error....
*/
public static class Employee implements Serializable {
String mName;
/**
* test error....
*/
private Employee(String name) {
mName = name;
}
public String toString() {
return "Employee mName:" + mName;
}
public static Employee create(String name) {
return new Employee(name);
}
}
}
private byte[] mDiscardBytes = new byte[] {
0x7a, 0x7a, 0x7a, 0x67, 0x67
};
private byte[] modifyBlockDataSize(byte[] content) {
for (int i=0; i
经过这个处理以后,得出的序列化数据如下: