Java序列化框架自测(一)

      项目中使用了memcached作为缓存,我们采用的客户端在序列化对象的时候,采用的是java内置的序列化方式。

ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(object); byte[] bytes = bos.toByteArray(); bos.close(); oos.close();

 

      java内置序列化的性能一直不为人所称道,考虑到使用memcached的读写频度很高,在这个过程中的损耗会很大,因此希望寻找一个替代方案,能够达到以下目标:

1. 序列化/反序列化 的时间成本降低,可以减少在序列化/反序列化上的时间消耗。

2. 序列化后的byte数组的空间成本降低,可以节省memcached存储空间,减少网络间的数据传输量。

3. 尽量降低使用复杂度,便于和当前工作整合。

 

      网上有个项目,是针对各种流行的java序列化框架做的benchmark,https://github.com/eishay/jvm-serializers/wiki/

给出了在测试数据情况下的各种序列化框架的时间空间性能,可以看到有很多现成的方案比java内置方式在时空性能上都有大幅度的提升。

 

      最先引起我注意的是kryo,在我刚开始看到这个benchmark的时候,kryo在时空性能上都是最为出色的,现在的benchmark加入了protostuff,情况有所改变,不过kryo仍然表现很抢眼。

 

      找到kryo项目地址:http://code.google.com/p/kryo/ ,看了下quickstart,还是比较容易上手的。kryo提供了对需要序列化的java类的注册功能,这个操作在kryo框架中,为注册类指定了一个id。这样可以在在序列化/反序列化时,使用该id标示对应的java类,而不是类名字符串。当然也可以使用kryo . setRegistrationOptional ( true ) 方式,避免注册类的操作,但是这个操作是要损失一些时空性能的。

 

      对于我们来说,由于是希望在我们封装的缓存操作层中实现该序列化/反序列化功能,需要对使用者屏蔽类型细节,因此后一种不需要注册的方式更适合我们。

 

      接下来是对kryo的测试工作,检验一下针对我们的数据,其性能表现如何。我准备使用某个需要缓存的典型场景下的 java类,这个类包含了多层次的嵌套类,int、string等多种数据类型,并且使用了list、map等数据结构。测试基准数据是420个该java类对象,并在测试中循环5次,也就是总共2100次操作,最后统计其时空成本。

 

      测试结果很出乎我的意料,下面是测试结果

1. java
byte length: 43367355
time spend: 2.643s

2. kryo
byte length: 79748810
time spend: 1.861s

 

      时间上确实提升了不少,有30%,但是空间上却多出了80%,这个现象是很奇怪的。于是我对那420个对象进行了排查,发现里面有30多个在使用kryo序列化后空间上比使用java的大,而且大很多。进一步对比发现,这30多个对象都有很多的字符串field,而且这些field是使用的相同的对象。由此我怀疑kryo在对同一对象的多个引用进行序列化时,是将其作为多个对象进行序列化的。因此我又设计了一个测试场景,代码如下

@Test public void testStringSer() throws IOException{ char[] chars = new char[1500]; Arrays.fill(chars, 'a'); String str = new String(chars); testStringSer(str); String[] strArray = new String[]{str, str, str}; testStringArraySer(strArray); } private void testStringSer(String str) throws IOException{ Kryo kryo = new Kryo(); kryo.setRegistrationOptional(true); ObjectBuffer ob = new ObjectBuffer(kryo, 5 * 1024); byte[] bytesKryo = ob.writeObjectData(str); System.out.println("kryo byte length of string:"+ bytesKryo.length); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(str); byte[] bytesJava = bos.toByteArray(); bos.close(); oos.close(); System.out.println("java byte length of string:"+bytesJava.length); } private void testStringArraySer(String[] strArray) throws IOException{ Kryo kryo = new Kryo(); kryo.setRegistrationOptional(true); ObjectBuffer ob = new ObjectBuffer(kryo, 5 * 1024); byte[] bytesKryo = ob.writeObjectData(strArray); System.out.println("kryo byte length of string array:"+ bytesKryo.length); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(strArray); byte[] bytesJava = bos.toByteArray(); bos.close(); oos.close(); System.out.println("java byte length of string array:"+bytesJava.length); }

 

测试结果如下:

kryo byte length of string:1502
java byte length of string:1507
kryo byte length of string array:4511
java byte length of string array:1557

 

同样的测试代码,换成Integer类型,数据为Integer.MAX_VALUE,测试结果为:

kryo byte length of int:5
java byte length of int:81
kryo byte length of int array:20
java byte length of int array:132

 

可以看出,这是目前kryo 1.04版本的一个bug,希望能在后续版本中解决这个问题。

你可能感兴趣的:(Java序列化框架自测(一))