关于java的socket有可能出现内存泄漏的问题

系统 1565 0
前段时间写了一个java socket相关的程序,大概意思就是client和server是采用socket长连接方式,之间通信都是通过通过ObjectOutputStream和OjbectInputStream来进行写和读操作。

其实以前就很多次的用到过ObjectOutputStream,不过没有详细的琢磨过,这次就想着琢磨一下,主要也是因为我发现程序中存在内存泄漏的问题,通过Jprobe跟踪,排除了别的泄漏因素,最后定位在是在socket这里发生了泄漏,具体情况下面进行分析。

先来说说ObjectOutputStream,通过ObjectOutputStream来进行socket写入,那么会在流中加入Object的信息,也就是说如果想要是跨平台的socket通信那么可能会带来一些问题,因为数据流中加入的是java特有的信息,class类型以及成员变量的类型信息,同样通过ObjectInputStream来读取也有相应的规则来进行解析。

下面我们来看一下写入的信息,首先有一个简单的类

    
class MyObject implements Serializable {
	private static final long serialVersionUID = -9163423175612080544L;
	String str1;
	String str2;
}

  


我们通过ObjectOutputStream来把该类写入文件中,查看一下写入的内容
    
 sr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test1t test2q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ 

  

我们可以看到写入的内容中包含了写入类的类型以及成员变量信息,当然关于插入的内容,我们可以覆盖ObjectOutputStream类的writeStreamHeader()方法来实现插入我们自定义的内容,当然如果这样做的话,我们就必须对ObjectInputStream类进行重写

上面是一些题外话,下面回到正题,关于我标题中提到的有内存泄漏的问题。为了更清晰直观的说明该问题,我又写了一个很简单的测试,代码如下:
    
            FileOutputStream fos = new FileOutputStream("c:\\test.txt");
	    ObjectOutputStream oos = new ObjectOutputStream(fos);

	    MyObject myObj = new MyObject();
	    myObj.str1 = "test1";
	    myObj.str2 = "test2";
	    for (int i = 0; i < 20; i++) {
	        oos.writeObject(myObj);
	        oos.writeObject(myObj);
	    }
	    fos.close();

  

我在这里循环了20次,那么大家可以猜想一下文件中会是什么内容,可能会有人认为是我刚才在上面贴出的内容重复20遍,起初我也是这么认为的,但事实不是这么回事。
我现在更改一下程序,不再循环,只写入一次,那么写入的内容是
    
 sr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test1t test2q ~ 

  

循环10次
    
 sr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test1t test2q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ 

  

循环20次
    
sr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test1t test2q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ q ~ 

  


这样的测试我们还是不够清晰,下面我们让他每次循环写入的对象的属性都不相同,我们这样修改一下代码
    
            FileOutputStream fos = new FileOutputStream("c:\\test.txt");
	    ObjectOutputStream oos = new ObjectOutputStream(fos);

	    for (int i = 0; i < 10; i++) {
	    	MyObject myObj = new MyObject();
	    	myObj.str1 = "test1" + i;
	    	myObj.str2 = "test2" + i;
	        oos.writeObject(myObj);
	        oos.writeObject(myObj);
	    }
	    fos.close();

  

那么这个时候文件里的内容是什么呢,我们再来看一下
    
 sr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test10t test20q ~ sq ~  t test11t test21q ~ sq ~  t test12t test22q ~ sq ~  t test13t test23q ~ sq ~  t test14t test24q ~ sq ~  t test15t test25q ~ sq ~  t test16t test26q ~ sq ~  t test17t test27q ~ sq ~  t test18t test28q ~ sq ~  t test19t test29q ~ 

  

这样就显而易见了,我们虽然写入了10次,但是不会每次写入都会插入写入对象和成员变量类型的信心,而是在第一次写入的时候插入一些头信息,以后再写就不会再插入了。这实际是java做的优化,通过该优化从而减少socket传输的开销。
那么会有人问了,你说的内存泄漏的问题呢,写到这里,我想应该有人已经看出问题来了,它之所以可以这么做优化,前提是持有MyObject的引用,也就是说,不会释放掉MyObject的引用。现在明白了吧,如果你是长连接的方式,ObjectOutputStream会一直持有你以前发送过的对象的引用,从而导致jvm在进行垃圾回收的时候不能回收之前发送的对象的实例,经过漫长时间的运行,最终导致内存溢出了。这一点从我通过Jprobe跟踪也得到了印证。

下面我们来谈谈如何避免该问题,说着这里我们就得提到ObjectOutputStream的reset方法了,JDK文档中是这么解释该方法的:
“重置将丢弃已写入流中的所有对象的状态。重新设置状态,使其与新的 ObjectOutputStream 相同。将流中的当前点标记为 reset,相应的 ObjectInputStream 也将在这一点重置。以前写入流中的对象不再被视为正位于流中。它们会再次被写入流。”
就是说调用reset那么就丢弃所持有对象的状态(也就是释放掉了对对象的应用),同时会在流中设置reset标识。

还是之前那个例子,我们来修改一下代码,在每次写入后都调用一下reset方法
    
            FileOutputStream fos = new FileOutputStream("c:\\test.txt");
	    ObjectOutputStream oos = new ObjectOutputStream(fos);

	    for (int i = 0; i < 10; i++) {
	    	MyObject myObj = new MyObject();
	    	myObj.str1 = "test1" + i;
	    	myObj.str2 = "test2" + i;
	        oos.writeObject(myObj);
	        oos.writeObject(myObj);
                oos.reset();
	    }
	    fos.close();

  

我们再来看一下写入文件内容
    
 sr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test10t test20q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test11t test21q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test12t test22q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test13t test23q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test14t test24q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test15t test25q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test16t test26q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test17t test27q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test18t test28q ~ ysr com.travelsky.test.MyObject€喳+?^` L str1t Ljava/lang/String;L str2q ~ xpt test19t test29q ~ y

  

这次跟之前不同的,每一次写入都加入了头信息且每一次末尾都加入了y,我想这个标识应该就是reset标识,至于具体是什么,我们没必要深究了。

通过上面一系列的测试,我们大概对Object流有了一定了解,那么具体到我们日常编码中到底该不该调用reset呢,这个我想不能一概而论了,我们通过测试也看到了,在不调用reset的方式下,java的优化对于减轻socket开销还是很可观的,当然代价是有的,那就是直到你调用reset或者是关闭输出流之前,对于发送过的对象的实例是不会释放的。

结论:当然只是我自己的片面之词。如果你的程序需要很长时间的运行,我建议你还是调用reset避免最后内存溢出程序崩溃,但是如果你又要长时间运行,且发送的消息量又很大,那么调用reset无疑会增加开销,那么这个时候最好的做法我觉得是你自己实现一套机制,定时的调用reset或者是定量,比如查看到内存已经涨到一个水平后调用一下,这样既可以避免内存无限的增长下去,又可以减少不少socket通信的开销

anson在这里感谢大家花了这么长时间阅读该文章,希望能给大家带了一些帮助,另外上面的分析都是我个人的理解,肯定存在一定的局限性,大家有什么更深刻的认识,还请大家指出来,我们一起交流,共同进步

关于java的socket有可能出现内存泄漏的问题


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论