Jun 11

Java HeapSpace OutOfMemory一例分析

        这两天经常听到同事说我们的系统运行一两个小时过后就会报Java HeapSpace OutOfMemory的异常,一开始我还没怎么在意这个问题,还以为是在eclipse中用debug状态运行tomcat导致的,后来又出现了几次,觉得事情有点儿不对了,检查了我的代码,基本没有什么地方会出现内存泄漏,该释放资源的地方也都释放了。一开始懒得去看同事的代码,就找了jconsole工具来分析,这不看不知道,一看才吓了一跳。查看内存占用发现大致每隔两分钟左右内存就会有一个飙升,线程数跟内存占用一样每当内存波动的时候线程数就有一个飙升,而且一上去了就不下来,查看具体的线程堆栈,发现增加的线程全部是一个snmp4j中的一个类型。这一块恰好是我那同事实现的,于是没办法只有看看他的代码了(貌似搞开发的都不愿意看别个的代码…). 在我们的系统中有一个轮询操作,在这个轮询操作中会使用到snmp4j api,查看snmp4j api发现用到的一个类中会持有socket资源并创建内部线程,这个socket会中断在那块儿,而俺那同事的实现是在每次轮询的时候都会新建一个该对象,而且在使用过后也没有close掉。查看snmp4j的源码发现该类的内部实现是有从一个内部线程池中取得一个线程,然后在该线程中用到socket,于是由于线程中的socket为关闭一直阻塞,这条新建的线程 也就没办法释放了。问题就算是找到了,下面的解决办法就很容易了,将这个地方实现为一个单例模式,最后测试的结果也很好,运行三四个小时,线程数始终保持在估计的范围内。

等有时间了,再好好写一篇jconsole的使用方法,发现这玩意儿还挺好玩儿的。

May 20

Process waitFor无法返回原因分析

    今天做一个mysql数据恢复的小程序,采用Runtime.exec的方式来执行mysql命令。主要用来恢复一些由定时程序自动备份出来的数据库文件,有时候可能会出现恢复的数据在数据库中还存在,于是会出现主键重复的错误,于是我在恢复的时候加入–force参数,手动在cmd控制台下执行可以很明显看到mysql返回了一些duplicate的error,按道理在java app中也能正常执行过去,可在执行java app的时候看控制台中打印的日志显示,程序阻塞在了waitFor这一步。我的程序片段如下:
……   
p = Runtime.getRuntime().exec(cmd);
……
    p.waitFor();
……
    百思不得其解,最后在google了下,找到一片最早分析该原因的文章是在javaworld上。原因是操作系统这一层对输入输出流有一个buffer,由于受到这个buffer的大小限制,当buffer满了而又没有线程去读,则当前执行的线程就会阻塞掉,于是修改一下程序,开辟两个线程专门用于读取输出流,一个用于读取返回消息流,一个用于读取异常消息流,于是修改程序如下:
……
p = Runtime.getRuntime().exec(cmd);
   
new PrintStream(p.getInputStream()).start();
new PrintStream(p.getErrorStream()).start();
p.waitFor();
……
此处PrintStream为一个读取inputstream的线程。

May 14

Java串口通信总结

    串口通信这词儿听上去跟java似乎有点儿沾不上边儿,因为java大多是拿来做大规模企业应用的,像串口通信这种很底层的东东一般都会用比较低级别的语言来做,但最近就遇到了这么一事儿,于是google了下,没想到还真能用java做,要用到的是javax扩展类库javacomm,它是一系列的标准,该类库在sun的官网上只提供linux版本。由于我所应用的平台是win32,所以还得去其他地儿找win32的实现,找了好久终于找到了。下面对其用法做一个简要说明。

    1.首先在下载到了javacomm包后,需要将其解压,解压后需要做的就是将java扩展库安装到本地开发环境以及运行环境中。对于开发环境需要做的就是将win32com.dll复制到%jdk install dir%/bin下,然后将comm.jar和javax.comm.properties拷贝到%jdk install dir%/lib下,对于运行环境要做的就是将这些文件拷贝到相应的jre下即可。

    2.javacomm API

    其API相当简单,下面看如下代码
   CommPortIdentifier serialPortId = CommPortIdentifier.getPortIdentifier(portName);
   log.info(“open serial port COM1 …”);
  SerialPort  port = (SerialPort) serialPortId.open(appName, timeout);
   log.info(“serial port COM1 configuration : ” + baudrate + ” , ” + dataBits + ” , ” + stopBits + ” , ” + parity);
   port.setSerialPortParams(baudrate, dataBits,stopBits, parity);
   port.enableReceiveTimeout(IOTIMEOUT);
   OutputStream out = port.getOutputStream();
   InputStream in = port.getInputStream();
   Request req = Request.getInstance();
   req.setIn(in);
   req.setOut(out);

    第一行取得一个名为portName的串口描述(看这个用法,这个地方有点儿像是单例跟工厂模式)。然后从串口描述中打开串口得到一个串口对象,注意这个地方的timeout参数是用来设置打开串口的超时时间,往后看可以看到还有一个receiveTimeout的设置,这个设置害我冥思苦想了好长时间。一开始没有注意到打开串口时可以设置这个timeout时间,使用的是一个单appName参数的方法,结果后来出现无法设置io超时时间的问题,而java API中也没有提供io超时时间的方法,最后终于一天晚上在给我的她打完电话后发现了这个苦苦期盼了好久的方法,问题迎刃而解了。之后就是设置通信的一些参数诸如波特率,数据位,停止位,校验方式等。然后就可以从串口中得到输入输出流了,这个地方要注意的是串口通信是全双工的,输入流跟输出流相互没有什么关系。

Jul 02

满江红正式发布Spring 2.5中文版手册

        满江红开放技术研究组织Leader曹晓刚感慨地说:“经过了4个月的努力,Spring 2.5 Reference的中文版终于在2008年7月1日凌晨正式发布了,感谢参加翻译工作的同学们这4个月来的支持。记得2.0中文版发布时是2006年的国庆,一晃已经快两年了,这两年里Spring在国内的应用发展非常迅速,相信我们的文档也在其中发挥了一定的作用。” Continue reading