《武汉工程大学学报》  2012年06期 74-78   出版日期:2012-06-30   ISSN:1674-2869   CN:42-1779/TQ
空气质量实时监测系统的内存泄漏


1问题描述 近几年来,随着经济的快速发展,特别是房地产业和汽车产业的发展,空气污染日益严重的问题引起了环保部门的重视.国家环保部主要针对空气中的PM10(可吸入颗粒物)、SO2、NO2、CO、O3等几种主要污染物进行实时监测,并要求地方环保部门每隔5 min上报一次各个自动化监测点的实时监测数据.空气质量实时监测系统要求将每5 min从数据库中读取一次数据,并将监测数据动态展示在Google地图上.空气质量实时监测系统的系统结构图如图1所示.图1空气质量实时监测系统结构图
Fig.1Air quality real-time monitoring
system structure chart  空气质量实时监测系统的工作流程为:实时数据更新程序定时将各个自动化监测站点的数据更新到中心站的数据库服务器中;GooleMap.aspx页面使用Microsoft AJAX框架实现页面的异步刷新机制,Timer控件每300 s自动回发页面到Web发布服务器取5 min的监测数据在Google地图的标记信息窗口显示,实践中有两种方案.方案1使用著名的开源代码网站http://www.codeplex.com中的免费开源控件GoogleMap Control for ASP.NET[1],控件的标记名称为Artem.Artem控件封装Google Map API,使得开发人员可以直接在aspx页的Code Behind代码中针对该控件公开的编程接口编程,从而控制Google地图的使用逻辑.方案2直接使用Google Map API[2-3],仍然使用AJAX的异步机制,通过调用Web服务取中心站数据库中的5 min数据更新Google地图中的标记信息窗口.两种方案都不同程度的存在内存泄漏[4-5]问题.首先分析两种方案产生内存泄漏的原因,然后针对这些原因提出解决方案,从而解决内存泄漏问题.2内存泄漏问题分析2.1方案1内存泄漏问题分析 方案1直接使用封装的GoogleMap控件,并通过对Artem控件公开的属性、方法、事件编程,从而控制Google地图中标记的信息窗口显示5 min的实时监测数据.实时监测数据显示效果如图2所示.图2实时监测数据显示
Fig.2Real-time monitoring data presentationGoogleMap.aspx页面内容为:……<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager><asp:UpdatePanel ID="UpdatePanel1" runat="server" >  <asp:Timer runat="server" id="tmUpdate" ontick="tmUpdate_Tick" Interval="300000" ></asp:Timer><artem:GoogleMap ID="gmMain" runat="server" Width="810px" Height="500px" Key="Google Map Key" Latitude="30.58" Longitude="114.31" Zoom="11" ZoomPanType="Large3D" EnableDragging="true" EnableContinuousZoom="true" EnableScrollWheelZoom="false" > <Markers></Markers> </artem:GoogleMap> </asp:UpdatePanel> ……  GoogleMap.aspx.cs后台代码内容为:protected void tmUpdate_Tick(object sender,EventArgs e){ gmMain.Markers.Clear();//清空以前添加的标记 foreach (t_SStation ts in 从数据库中取得站点列表) { GoogleMarker gk = new GoogleMarker(站点经度,站点纬度); //实例化站点标记第6期刘黎志,等:空气质量实时监测系统的内存泄漏
武汉工程大学学报第34卷
gk.Title = ts.SStationName; //设置站点标题 gk.Text = “信息窗口中的显示内容,为从数据库中取得的5 min监测数据值”;  gmMain.Markers.Add(gk);}} 方案1设计页面每5 min回发一次,在后台代码中的tmUpdate_Tick事件中从中心站数据库中取得各个监测站点5 min的监测值,使用最新的5 min值更新地图标记的信息窗口.此方案从功能实现的角度看,是最简单的实现方法,但该方案内存泄漏严重,几乎每次回发,Windows任务管理器显示承载Google地图的IE浏览器的iexplore.exe进程的内存使用增加10 MB左右.若以系统可用内存为2 GB计算,2 048 MB/(10 MB Gbyte*(60/5))=17 h后,系统内存耗尽.分析原因认为,该控件的设计者在每次页面刷新时并没有释放掉前一次的GoogleMap实例,而是又重新生成了一个实例,故产生内存泄漏.2.2方案2的内存泄漏问题分析 由于方案1中的Artem控件是封装发布的,从编程的角度出发,已经无法解决其在定时刷新数据时产生的内存泄漏问题.故只能直接采用Google Map API,用Javascript脚本语言编程实现.具体实现描述如下.* GoogleMap.aspx页面内容为:……<asp:ScriptManager ID="Script Manager1" runat="server">//定义Web服务调用地址<Services> <asp:ServiceReference Path="~/MapService/MapDataWS.asmx"/></Services>//定义脚本语言调用地址 <Scripts> <asp:ScriptReference Path="~/Scripts/MapDataWS.js" /></Scripts> </asp:ScriptManager><div id="map_canvas" style="width:810px; height:520px;"></div> ……//定义Google地图显示层* MapDataWS.asmx Web服务内容为:public List<Station> GetStations(){从数据库从取得监测站点的基本信息,包括名称、经度、纬度等,以集合形式返回; }public List<StationCurData> GetCurData(){从数据库中取每个站点5 min的实时监测值,以集合形式返回,与GetStation()返回的集合一一对应; }* MapDataWS.js脚本内容为:var mdsProxy; //定义全局Web服务代理类 var map; //定义全局Google地图实例var sBasicInfo; //定义全局站点基本信息集合 function pageLoad(sender,args) { if (!args.get_isPartialLoad()) { mdsProxy = new MapDataWS();// 初始化Web服务代理类 var latlng = new google.maps.LatLng(30.58,114.31); //定义Google地图 var myOptions = { zoom: 9,center: latlng,mapTypeId: google.maps.MapTypeId.ROADMAP }; map = new google.maps.Map($get(’map_canvas’),myOptions); mdsProxy.GetStations();//获取站点基本信息集合  window.setInterval(mdsProxy.GetCurData(),300000); //设置定时刷新,每5 min取一次监测值}}function SucceededCallback(result,userContext,methodName) { switch(methodName) {case ("GetStations"):{ sBasicInfo = result; break;}//获取返回的站点集合 case ("GetCurData")://获取5 min实时监测数据,并生成与之对应的图标和信息窗口 { for (i in result) { //定义站点的经纬度位置对象 var Latlng = new google.maps.LatLng(sBasicInfo[i].SLatitude,sBasicInfo[i].SLongitude); //根据站点基本信息定义地图标记 var mark = new google.maps.Marker({ position: Latlng,map: map,title: sBasicInfo[i].SStationName }); //用取得的5 min监测数据定义信息窗口显示内容 var contentString = result[i].SStationName + "PM10: " + result[i].PM10.toString() + ……. //定义信息窗口,及与地图标记相关联的click事件 var infowindow = new google.maps.InfoWindow({ content: contentStr }); google.maps.event.addListener(mark,’click’,function () { infowindow.open(map,mark); }); break;}} 方案2使用Google Map API直接在脚本中定义地图、地图标记及与地图标记对应的信息窗口,保证了地图实例对象只在页面初始化时定义一次,这就避免了方案1中每次页面刷新就重复定义一次地图实例的问题.但经过仔细观察,仍然存在内存泄漏问题,只是没有方案1那么明显,大约每回发两次,Windows任务管理器显示承载Google地图的IE浏览器的iexplore.exe进程的内存使用增加1 MB左右.以系统可用内存为2 GB计算,2 048 MB/(0.5 MB*(60/5))=341 h后,系统内存耗尽,还是不能满足不间断实时监测的需要.经分析得出,虽然地图实例没有在每次页面刷新时生成,但仍然生成地图标记对象、信息窗口对象,且前次生成的地图标记及信息窗口对象均没有被IE浏览器释放,故产生内存泄漏.3内存泄漏问题的解决 分析方案1及方案2产生内存泄漏的原因,得出的结论是IE浏览器不会自动的回收由Javascript脚本语言定义的Google地图、标记、信息窗口实例对象,故不论采用何种方式定时刷新页面,只要存在实例对象的重复定义,就会产生内存泄漏.由于不知道IE浏览器如何回收自定义对象实例的机制,故要避免内存泄漏,解决的方案只有避免对象的重复定义,具体的方法描述如下: 即在方案2的MapDataWS.js脚本全局定义部分,增加var infowindowArray = []及var markerArray = []全局数组定义,分别表示信息窗口数组及地图标记数组,用于保存和各个监测站点对应的地图标记实例对象及信息窗口对象.改进的更新算法为:function SucceededCallback(result,userContext,methodName) {switch(methodName){ …… case ("GetCurData"): { if (infowindowArray.length == 0) {//保证只生成一次标记实例、信息窗口实例 for (var i in sBasicInfo) { var Latlng = new google.maps.LatLng(sBasicInfo[i].SLatitude,sBasicInfo[i].SLongitude); var mark = new google.maps.Marker({ position: Latlng,map: map,title: sBasicInfo[i].SStationName }); //保存标记实例到markerArray数组 markerArray.push(mark); var infowindow = new google.maps.InfoWindow({ content: MakeInfoWindowContent(i,result) }); //保存信息窗口实例到infowindowArray数组 infowindowArray.push(infowindow); google.maps.event.addListener(mark,’click’,function () { infowindow.open(map,mark); }); }} //页面定时读取各个监测站点5 min值,更新对应的信息窗口内容,但不再重复生成信息窗口实例 else {for (var i in result) {infowindowArray[i].setContent(MakeInfoWindowContent(i,result));}}break; } …… 其中MakeInfoWindowContent函数根据监测站点5 min值, 生成信息窗口展示的信息,具体实现不再描述.由于地图、标记、信息窗口对象实例均只生成一次,系统在IE浏览器中运行时,Windows任务管理器显示承载Google地图的IE浏览器的iexplore.exe进程的内存不再增加,内存泄漏问题得到解决.各方案的iexplore.exe进程的内存泄漏对比如图3所示,随着时间的增加,没有内存使用增长则表示没有内存泄漏.



图3内存使用对比图
Fig.3Memory using comparison chart4结语 利用地理位置信息来展示与之相关数据信息变化的技术在Internet网上已经得到广泛应用,特别是通过定时异步刷新数据信息,用户可以直接通过Web浏览器实时观察数据变化,地理信息和数据展示就更加的生动形象.IE浏览器不会主动释放由Javascript脚本语言声明的对象实例,从而导致内存泄漏.如果这个问题不解决,数据的实时监控就无法实现连续不间断运行.通过对空气质量实时监测系统中内存泄漏的问题的分析,找到导致系统内存泄漏的原因,并最终解决内存泄漏问题,希望对从事相关工作的同仁有所借鉴.