论坛首页 Java企业应用论坛

一段代码引起项目失败

浏览 19197 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (3) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-03-10   最后修改:2011-03-10

  因为一个小小的bug导致失败,记录下来,由于平台是公司自有业务,所以是处于不断开发和维护中的,难免前人挖坑后人跌倒。

  上代码:

    代码片段1:

   

Map infoMap = new HashMap();
SendMessageParam sendMessageParam = buildSendMessageParam(map,infoMap);
...
...
//很长一段逻辑
...
...    			
infoMap.put("sendMessageParam", sendMessageParam);
    			

   buildSendMessageParam函数处理方法:

    代码片段2:

   

SendMessageParam buildSendMessageParam(Map map, Map infoMap) {
	SendMessageParam sendMessageParam = new SendMessageParam(infoMap);
	...
                ...
                ...	
                return sendMessageParam;
}

    SendMessageParam类的构造方法:

    代码片段3:

   

public SendMessageParam(Map<String, Object> data) {
        this.data = data;
}

    

    代码片段2和代码片段3是工程师1编写的,代码片段1是工程师2编写的。很明显工程师2没有理解工程师1的目的,也没有仔细阅读代码,所以写出了Map自引用的bug。当然工程师1对代码片段3的处理上是有问题的。

    代码运行近半年没有问题,虽然Map自引用会导致垃圾难以回收(注:希望通过buildSendMessageParam生成新对象进行操作,而“缓存”老对象的话或者始终存在从根到infoMap的引用链),但是因为基本每天都有项目或者小需求发布虚拟机重启,内存溢出没有暴露出来;或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。

   

    一切似乎很正常,但是悲剧发生在新年的第一个项目上,工程师3为了在日志里记录infoMap的数据以备分析,做出了以下代码:

   

public class SendMessageParam implements IVa...
...
...
@Override
    public String toString() {
        return "SendMessageParam [isKeepVar=" + isKeepVar + ",  ...
               ....
                + ", data=" + data + "]";
    }

 

   相等于:infoMap.toStirng();

   

    就一个toStirng()方法,打开了潘多拉魔盒,当系统中该代码在第二天被运行到后,后台不停的抛出:

    java.lang.StackOverflowError

    因为改动的东西不起眼,所以工程师3没有进行单元测试覆盖(属于搭本次项目顺风车的),却导致了本次项目的发布失败。

     原因很简单:

    

日志打印时调用了String类的
public static String valueOf(Object obj) {
 return (obj == null) ? "null" : obj.toString();
}
方法。obj.toString()方法调用的是HashMap的toString()方法。HashMap继承了父类的toString方法:public class HashMap<K,V>
    extends AbstractMap<K,V>

AbstractMap的toString方法:

  

public String toString() {
	Iterator<Entry<K,V>> i = entrySet().iterator();
	if (! i.hasNext())
	    return "{}";

	StringBuilder sb = new StringBuilder();
	sb.append('{');
	for (;;) {
	    Entry<K,V> e = i.next();
	    K key = e.getKey();
	    V value = e.getValue();
	    sb.append(key   == this ? "(this Map)" : key);
	    sb.append('=');
	    sb.append(value == this ? "(this Map)" : value);
	    if (! i.hasNext())
		return sb.append('}').toString();
	    sb.append(", ");
	}
    }

 

    当Map中存在对象的时候:sb.append(value == this ? "(this Map)" : value);
    会调用:String类的toString()方法,这样当HashMap中存在自引用的时候就出现了死循环。

   

 

   

   发表时间:2011-03-10  
简单的来说就是:     Map map1 = new HashMap();
Map map2 = new HashMap();
map1.put("map", map2);
map2.put("Map0", map1);
System.out.println(map1);
0 请登录后投票
   发表时间:2011-03-10  
题目有点大啊

只是发布失败,项目没有以失败告终吧
0 请登录后投票
   发表时间:2011-03-10   最后修改:2011-03-10
llyzq 写道
题目有点大啊

只是发布失败,项目没有以失败告终吧

在预定的时间,没有达到预期的效果,就是失败,何况还产生了项目回滚,至少对现在来说它失败了
0 请登录后投票
   发表时间:2011-03-10  
自引用并不会导致内存泄漏,现代jvm的gc算法都能处理这种循环引用的问题了。一个打印语句暴露隐藏的bug,还是很划算。
4 请登录后投票
   发表时间:2011-03-10  
dennis_zane 写道
自引用并不会导致内存泄漏,现代jvm的gc算法都能处理这种循环引用的问题了。一个打印语句暴露隐藏的bug,还是很划算。

我已经说明了  或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。 当jvm发现自引用Map不存在和根的引用链的时候
0 请登录后投票
   发表时间:2011-03-10  
chenyongxin 写道
dennis_zane 写道
自引用并不会导致内存泄漏,现代jvm的gc算法都能处理这种循环引用的问题了。一个打印语句暴露隐藏的bug,还是很划算。

我已经说明了  或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。 当jvm发现自引用Map不存在和根的引用链的时候



你说的是或者,我针对的是或者前面。

 

0 请登录后投票
   发表时间:2011-03-10  
dennis_zane 写道
chenyongxin 写道
dennis_zane 写道
自引用并不会导致内存泄漏,现代jvm的gc算法都能处理这种循环引用的问题了。一个打印语句暴露隐藏的bug,还是很划算。

我已经说明了  或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。 当jvm发现自引用Map不存在和根的引用链的时候



你说的是或者,我针对的是或者前面。

 


    如果自引用所在的类没有结束(该类和根存在引用链),也就是说Map自引用还存在和根的引用链,这个时候它是不会被垃圾回收的。即:

    类A被类B强引用,只有类B被回收后才会回收类A

0 请登录后投票
   发表时间:2011-03-10  
chenyongxin 写道
dennis_zane 写道
chenyongxin 写道
dennis_zane 写道
自引用并不会导致内存泄漏,现代jvm的gc算法都能处理这种循环引用的问题了。一个打印语句暴露隐藏的bug,还是很划算。

我已经说明了  或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。 当jvm发现自引用Map不存在和根的引用链的时候



你说的是或者,我针对的是或者前面。

 


    如果自引用所在的类没有结束(该类和根存在引用链),也就是说Map自引用还存在和根的引用链,这个时候它是不会被垃圾回收的。即:

    类A被类B强引用,只有类B被回收后才会回收类A

 

我没有说你后半句的理解错误,问题在于你下面这句话给人误导:


代码运行近半年没有问题,虽然Map自引用会导致垃圾难以回收,但是因为基本每天都有项目或者小需求发布虚拟机重启,内存溢出没有暴露出来;或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。

 

我想说的是:自引用并不会导致垃圾难以回收,哪怕你不经常重启,内存泄漏也不一定会暴露出来。该怎么回收还是怎么回收,该内存泄漏还是内存泄漏,没有必然联系。

 

 

0 请登录后投票
   发表时间:2011-03-10  
dennis_zane 写道
chenyongxin 写道
dennis_zane 写道
chenyongxin 写道
dennis_zane 写道
自引用并不会导致内存泄漏,现代jvm的gc算法都能处理这种循环引用的问题了。一个打印语句暴露隐藏的bug,还是很划算。

我已经说明了  或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。 当jvm发现自引用Map不存在和根的引用链的时候



你说的是或者,我针对的是或者前面。

 


    如果自引用所在的类没有结束(该类和根存在引用链),也就是说Map自引用还存在和根的引用链,这个时候它是不会被垃圾回收的。即:

    类A被类B强引用,只有类B被回收后才会回收类A

 

我没有说你后半句的理解错误,问题在于你下面这句话给人误导:


代码运行近半年没有问题,虽然Map自引用会导致垃圾难以回收,但是因为基本每天都有项目或者小需求发布虚拟机重启,内存溢出没有暴露出来;或者代码片段1所在的线程完成任务后Map自引用失去了引用链而被垃圾回收。

 

我想说的是:自引用并不会导致垃圾难以回收,哪怕你不经常重启,内存泄漏也不一定会暴露出来。该怎么回收还是怎么回收,该内存泄漏还是内存泄漏,没有必然联系。

 

 

  哦 呵呵,是我没有写清楚,我想表达的意思是因为虚拟机会经常重启,Map自引用就算没有被垃圾回收(存在和根的引用链的情况下),因为它占的内存比较小,不是很容易暴露内存溢出。

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics