bug说明


子类和父类中有同名的字段时,hessian反序列化会丢失字段值。 父类Person

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Person{");
        sb.append("name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }
}

子类PersonEx

public class PersonEx  extends  Person{
    private static final long	serialVersionUID	= 1L;
    private String	name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("PersonEx{");
        sb.append("name='").append(name).append('\'');
        sb.append('}');
        sb.append(super.toString());
        return sb.toString();
    }
}

当我们序列化如下对象后

  PersonEx p = new PersonEx();
  p.setName("XXX");
  p.setAge(28);

后,反序列化回来的PersonEx对象getName()==null.

原因分析


造成这个问题的原因如下: hessian序列化机制,他通过反射把所有字段名和值(整个传输对象图)读取出来,见JavaSerializer#writeObject:

   	if (ref == -1) {
	//写入需要反射的字段名
      writeDefinition20(out);
      out.writeObjectBegin(cl.getName());
   	}
	//写入值
	writeInstance(obj, out);

按照此规则,现在序列化结构如下(忽略其他映射关系):

name|age|name|Person|XXX|28|null

反序列化时,读取字段名,在set字段值:

  1. 读取到name字段,调用setName("XXX")
  2. 再次读取name字段,调用setName(null)

结果就囧了。。。。

解决办法


1.修改hessian代码

修改了hessian的序列化机制,当类的父对象出现相同的字段名时,跳过对此字段的处理。

 //添加成员变量propertiesNameSet	 
   	 private HashSet<String> propertiesNameSet = Sets.newHashSet();
 //在JavaSerializer构造器里,遍历字段时,添加检查
 if (propertiesNameSet.contains(field.getName())) {
       continue;
 } else {
      propertiesNameSet.add(field.getName());
 }

hessian在反序列化时有同名字段的判断com.caucho.hessian.io.JavaDeserializer#getFieldMap保证fieldMap中不出现同名的字段。

2.修改同名字段问题

出现了同名字段,也可以认为是我们代码设计出问题了,没有理解清楚继承结构。在子类中删除同名的字段,就避免这个bug。

小结

现在的序列化框架基本上都会遇到此问题,Kryo中的com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer也没有对此问题进行处理.作为框架开发者,要尽量减少框架使用者犯错误的机会,也就是所谓的防御性编程吧.

Kryo pull request ,防止因为子类和父类中有同名field name而导致的问题.

传输对象如下:

public class EmailSenderOrder {
private String clientName="test";
//clientName默认值为test
}

应用场景需要传输的clientName为null,我们在使用client时,会有如下代码:

EmailSenderOrder emailSenderOrder = new EmailSenderOrder(); 
emailSenderOrder.setClientName(null); 

服务端在接受到对象后,clientName结果等于了test。 默认情况下,如果field为null,cxf client生成的报文中没有这个field的报文,这就导致在反序列化时,初始化对象时用了默认值。

可以如下解决:

//通过字段序列化
@XmlAccessorType(XmlAccessType.FIELD) 

public class EmailSenderOrder {

//如果clientName为null,生成报文<clientName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
@XmlElement(name = "clientName", nillable = true) 
private String clientName="test";

}

Netty 4 at Twitter: Reduced GC Overhead

https://blog.twitter.com/2013/netty-4-at-twitter-reduced-gc-overhead

主要讲了netty4在减少gc压力和内存带宽消耗上的改进: 新消息收到或者发送消息时netty3会创建一个buffer,创建字节数组new byte[capacity],并用0来填充数组,这样会造成gc压力和内存带宽消耗,nettry4中为不同的事件定义不同的处理方法,减少事件对象创建。 netty4中引入新接口ByteBufAllocator,它提供一个buffer池(类似于jemalloc ),实现buddy memory allocation and slab allocation.

## Buddy memory allocation

https://en.wikipedia.org/wiki/Buddy_memory_allocation

buddy memory allocation是一种内存分配算法,它把内存划分为不同的分区,尽量满足不同的内存请求。最常见的是binary buddies,每一个内存block有一个order,order从0到某个值,在不同的order的block按照2order 的大小比例 ,所以满足 orderA=orderB-1 ,block B的大小为blockA的两倍。 首先需要确定最小block的大小(最小的可被分配的内存块)。最小快太小,操作系统会消耗过多内存和计算资源去跟踪内存块分配和回收。最小快比较大,又造成内存浪费。最小block的大小作为order0 block大小。

Slab allocation

https://en.wikipedia.org/wiki/Slab_allocation

slab allocation是一种内存管理机制,用于有效的内存分配,并尽量消除分配和回收内存过程中造成的碎片。它按照预先规定的大小,将分配的内存分割成特定长度的内存块,再把尺寸相同的内存块分成组,这些内存块不会释放,可以重复利用。

Scalable memory allocation using jemalloc

https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919

TCMalloc优化MySQL、Nginx、Redis内存管理

更好的内存管理-jemalloc

Heartbeat In Persistent Connection

http://cheney-mydream.iteye.com/blog/1497152

http://bbs.csdn.net/topics/360072641

TCP连接建立过后,如果没有心跳,时间长了就会产生“僵尸连接”,就是通信的双方其实连接已经断了,但由于TCP并不定时检测连接是否中断,而通信的双方又相互没有send操作,导致该连接在通信的双方的tcp上一直有效,占用操作系统资源。这时TCP连接是不可使用的,但是对于应用层并不知道,心跳包主要也就是用于长连接的保活和断线处理。

在rabbitmq client中,为了保证连接可用使用HeartbeatSender来定时发送心跳包。同样的,在druid中,可以配置在连接空闲达到阀值时,执行简单的sql来检测连接是否有效。

Nashorn(Naz-horn)

https://oracleus.activeevents.com/2013/connect/sessiondetail.ww?session_id=7835

介绍了noshorn一些用法 Shell Scripting部分介绍了使用Noshorn来编写shell 脚本,以后可以摆脱bash了。其他包括Noshorn中java互操性、线程、调试等。

https://oracleus.activeevents.com/2013/connect/sessionDetail.ww?SESSION_ID=5793

介绍了Noshorn是什么:

  1. JVM上的javascript引擎
  2. 100% java实现
  3. 终极invokedynamic用户
  4. 100%编译成字节码执行
  5. 100% 兼容ECMASCRIPT5.1

没有浏览器API(HTML5 canvas、HTML5 canvas、WebWorkers WebSockets、WebGL)

https://oracleus.activeevents.com/2013/connect/sessionDetail.ww?SESSION_ID=2585

介绍了JVM上的内嵌脚本语言,JSR-223 (javax.script规范),其中提到了使用script语言结合动态代理,并实时检测脚本文件变动实现live reloading。