日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
NET內(nèi)存持續(xù)增長問題排查

 一、背景

我們提供的服務(wù)有:成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、畢節(jié)ssl等。為數(shù)千家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的畢節(jié)網(wǎng)站制作公司

      在某個(gè)NET程序的測(cè)試過程中,發(fā)現(xiàn)該程序的內(nèi)存持續(xù)增長,無法釋放,直到程序關(guān)閉后才能釋放。經(jīng)排查,確定問題的根源是在調(diào)用WCF服務(wù)的實(shí)現(xiàn)代碼中,下面通過簡單代碼來重現(xiàn)問題發(fā)生的過程。

      1、服務(wù)端代碼,只提供GetFile操作,返回相對(duì)較大的內(nèi)容,便于快速看到內(nèi)存持續(xù)增長的過程。

 
 
  1. class Program 
  2.     { 
  3.         static void Main(string[] args) 
  4.         { 
  5.             using (ServiceHost host = new ServiceHost(typeof(FileImp))) 
  6.             { 
  7.                 host.AddServiceEndpoint(typeof(IFile), new WSHttpBinding(), "http://127.0.0.1:9999/FileService"); 
  8.                 if (host.Description.Behaviors.Find() == null) 
  9.                 { 
  10.                     ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); 
  11.                     behavior.HttpGetEnabled = true; 
  12.                     behavior.HttpGetUrl = new Uri("http://127.0.0.1:9999/FileService/metadata"); 
  13.                     host.Description.Behaviors.Add(behavior); 
  14.                 } 
  15.                 host.Opened += delegate 
  16.                  { 
  17.                      Console.WriteLine("FileService已經(jīng)啟動(dòng),按任意鍵終止服務(wù)!"); 
  18.                  }; 
  19.                 host.Open(); 
  20.                 Console.Read(); 
  21.             } 
  22.         } 
  23.     } 
  24.  
  25.     class FileImp : IFile 
  26.     { 
  27.         static byte[] _fileContent = new byte[1024 * 8]; 
  28.  
  29.         public byte[] GetFile(string fileName) 
  30.         { 
  31.             int loginID = OperationContext.Current.IncomingMessageHeaders.GetHeader("LoginID", string.Empty); 
  32.             Console.WriteLine(string.Format("調(diào)用者ID:{0}", loginID)); 
  33.             return _fileContent; 
  34.         } 
  35.     } 

 

      2、客戶端代碼,循環(huán)調(diào)用GetFile操作,在調(diào)用前給消息頭添加一些登錄信息。另外為了避免垃圾回收機(jī)制執(zhí)行的不確定性對(duì)內(nèi)存增長的干擾,在每次調(diào)用完畢后,強(qiáng)制啟動(dòng)垃圾回收機(jī)制,對(duì)所有代進(jìn)行垃圾回收,確保增長的內(nèi)存都是可到達(dá),無法對(duì)其進(jìn)行回收。

 
 
  1. class Program 
  2.     { 
  3.         static void Main(string[] args) 
  4.         { 
  5.             int callCount = 0; 
  6.             int loginID = 0; 
  7.             while (true) 
  8.             { 
  9.                 using (ChannelFactory channelFactory = 
  10.                     new ChannelFactory(new WSHttpBinding(), "http://127.0.0.1:9999/FileService")) 
  11.                 { 
  12.                     IFile fileProxy = channelFactory.CreateChannel(); 
  13.                     using (fileProxy as IDisposable) 
  14.                     { 
  15.                         //OperationContext.Current = new OperationContext(fileProxy as IContextChannel); 
  16.                         OperationContextScope scope = new OperationContextScope(fileProxy as IContextChannel); 
  17.                         var loginIDHeadInfo = MessageHeader.CreateHeader("LoginID", string.Empty, ++loginID); 
  18.                         OperationContext.Current.OutgoingMessageHeaders.Add(loginIDHeadInfo); 
  19.                         byte[] fileContent = fileProxy.GetFile(string.Empty); 
  20.                     } 
  21.                 } 
  22.                 GC.Collect();//強(qiáng)制啟動(dòng)垃圾回收 
  23.                 Console.WriteLine(string.Format("調(diào)用次數(shù):{0}", ++callCount)); 
  24.             } 
  25.         } 
  26.     } 

 

二、分析排查

      要解決內(nèi)存持續(xù)增長的問題,首先需要定位問題,才能做相應(yīng)的修復(fù)。對(duì)于邏輯簡單的代碼,可以簡單直接通過排除法來定位問題代碼所在,對(duì)于錯(cuò)綜復(fù)雜的代 碼,就需要耗費(fèi)一定時(shí)間了。當(dāng)然除了排除法,還可以借助內(nèi)存檢測(cè)工具來快速定位問題代碼。對(duì)于.net平臺(tái),微軟提供.net輔助工具CLR Profiler幫助我們的性能測(cè)試人員以及研發(fā)人員,找到內(nèi)存沒有及時(shí)回收,占著內(nèi)存不釋放的方法。監(jiān)測(cè)客戶端程序運(yùn)行的結(jié)果如下:

      從上圖可看到OperationContextScope對(duì)象占用了98%的內(nèi)存,當(dāng)前OperationContextScope對(duì)象持有256個(gè) OperationContextScope對(duì)象的引用,這些OperationContextScope對(duì)象總共持有258個(gè) OperationContext的引用,每個(gè)OperationContext對(duì)象持有客戶端代理的相關(guān)對(duì)象引用,導(dǎo)致每個(gè)客戶端代理產(chǎn)生的內(nèi)存在使用 完畢后都無法得到釋放。

三、問題解決

      OperationContextScope類主要作用是創(chuàng)建一個(gè)塊,其中 OperationContext 對(duì)象在范圍之內(nèi)。也就是說創(chuàng)建一個(gè)基于OperationContext 的上下文范圍,在這范圍內(nèi)共享一個(gè)相同的OperationContext對(duì) 象。這種上下文的特性是支持嵌套的,即一個(gè)大的上下文范圍內(nèi)可以有若干個(gè)小的上下文范圍,而且不會(huì)造成相互不干擾。所以如果沒顯式調(diào)用該對(duì)象的 Dispose方法結(jié)束當(dāng)前上下文恢復(fù)前一上下文,再利用OperationContextScope類創(chuàng)建新的上下文,就會(huì)一直嵌套下去。所以在這里應(yīng) 該要顯式調(diào)用Dispose方法結(jié)束當(dāng)前OperationContextScope上下文范圍,這樣可以解決內(nèi)存持續(xù)增長的問題了。

 
 
  1. class Program 
  2.     { 
  3.         static void Main(string[] args) 
  4.         { 
  5.             int callCount = 0; 
  6.             int loginID = 0; 
  7.             while (true) 
  8.             { 
  9.                 using (ChannelFactory channelFactory = 
  10.                     new ChannelFactory(new WSHttpBinding(), "http://127.0.0.1:9999/FileService")) 
  11.                 { 
  12.                     IFile fileProxy = channelFactory.CreateChannel(); 
  13.                     using (fileProxy as IDisposable) 
  14.                     { 
  15.                         //OperationContext.Current = new OperationContext(fileProxy as IContextChannel); 
  16.                         using (OperationContextScope scope = new OperationContextScope(fileProxy as IContextChannel)) 
  17.                         { 
  18.                             var loginIDHeadInfo = MessageHeader.CreateHeader("LoginID", string.Empty, ++loginID); 
  19.                             OperationContext.Current.OutgoingMessageHeaders.Add(loginIDHeadInfo); 
  20.                         } 
  21.                         byte[] fileContent = fileProxy.GetFile(string.Empty); 
  22.                     } 
  23.                 } 
  24.                 GC.Collect();//強(qiáng)制啟動(dòng)垃圾回收 
  25.                 Console.WriteLine(string.Format("調(diào)用次數(shù):{0}", ++callCount)); 
  26.             } 
  27.         } 
  28.     } 

 

 四、問題根源

      OperationContextScope為什么能持有大量的OperationContext引用?從CLR Profiler工具獲取的結(jié)果中可以看到OperationContextScope對(duì)象通過其內(nèi)部OperationContextScope對(duì)象來 持有大量OperationContext對(duì)象引用,可以推斷該類應(yīng)該有一個(gè)OperationContextScope類型的字段。下面看一下OperationContextScope類的源碼。

 
 
  1. public sealed class OperationContextScope : IDisposable 
  2.     { 
  3.         [ThreadStatic] 
  4.         static OperationContextScope currentScope; 
  5.   
  6.         OperationContext currentContext; 
  7.         bool disposed; 
  8.         readonly OperationContext originalContext = OperationContext.Current; 
  9.         readonly OperationContextScope originalScope = OperationContextScope.currentScope; 
  10.         readonly Thread thread = Thread.CurrentThread; 
  11.   
  12.         public OperationContextScope(IContextChannel channel) 
  13.         { 
  14.             this.PushContext(new OperationContext(channel)); 
  15.         } 
  16.   
  17.         public OperationContextScope(OperationContext context) 
  18.         { 
  19.             this.PushContext(context); 
  20.         } 
  21.   
  22.         public void Dispose() 
  23.         { 
  24.             if (!this.disposed) 
  25.             { 
  26.                 this.disposed = true; 
  27.                 this.PopContext(); 
  28.             } 
  29.         } 
  30.   
  31.         void PushContext(OperationContext context) 
  32.         { 
  33.             this.currentContext = context; 
  34.             OperationContextScope.currentScope = this; 
  35.             OperationContext.Current = this.currentContext; 
  36.         } 
  37.   
  38.         void PopContext() 
  39.         { 
  40.             if (this.thread != Thread.CurrentThread) 
  41.                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SFxInvalidContextScopeThread0))); 
  42.   
  43.             if (OperationContextScope.currentScope != this) 
  44.                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SFxInterleavedContextScopes0))); 
  45.   
  46.             if (OperationContext.Current != this.currentContext) 
  47.                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SFxContextModifiedInsideScope0))); 
  48.   
  49.             OperationContextScope.currentScope = this.originalScope; 
  50.             OperationContext.Current = this.originalContext; 
  51.   
  52.             if (this.currentContext != null) 
  53.                 this.currentContext.SetClientReply(null, false); 
  54.         } 
  55.     } 

 

      當(dāng)前的上下文對(duì)象由線程***的靜態(tài)字段currentScope持有,其實(shí)例字段originalScope保持前一上下文對(duì)象的引用,如果使用完畢后不 結(jié)束當(dāng)前上下文范圍,就會(huì)一直嵌套下去,導(dǎo)致所有OperationContext對(duì)象都保持可到達(dá),垃圾回收機(jī)制無法進(jìn)行回收,從而使得內(nèi)存持續(xù)增長, 直到內(nèi)存溢出。

     

五、總結(jié)

      類似OperationContextScope,TranscationScope以XXXScope結(jié)尾的類都可以看作Context+ContextScope的設(shè)計(jì)方式(參考Artech大神的博文:Context+ContextScope——這是否可以看作一種設(shè)計(jì)模式?),用于在同一范圍內(nèi)共享同一事物或?qū)ο?。在使用這類上下文對(duì)象的時(shí)候,確保使用using關(guān)鍵字來使得上下文范圍邊界可控。

 


本文名稱:NET內(nèi)存持續(xù)增長問題排查
URL分享:http://m.5511xx.com/article/dhpgjig.html