日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

NET内存持续增长问题排查

發布時間:2025/3/8 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 NET内存持续增长问题排查 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、背景

? ? 在某個NET程序的測試過程中,發現該程序的內存持續增長,無法釋放,直到程序關閉后才能釋放。經排查,確定問題的根源是在調用WCF服務的實現代碼中,下面通過簡單代碼來重現問題發生的過程。

? ??1、服務端代碼,只提供GetFile操作,返回相對較大的內容,便于快速看到內存持續增長的過程。

  • class?Program?
  • ????{?
  • ????????static?void?Main(string[]?args)?
  • ????????{?
  • ????????????using?(ServiceHost?host?=?new?ServiceHost(typeof(FileImp)))?
  • ????????????{?
  • ????????????????host.AddServiceEndpoint(typeof(IFile),?new?WSHttpBinding(),?"http://127.0.0.1:9999/FileService");?
  • ????????????????if?(host.Description.Behaviors.Find<ServiceMetadataBehavior>()?==?null)?
  • ????????????????{?
  • ????????????????????ServiceMetadataBehavior?behavior?=?new?ServiceMetadataBehavior();?
  • ????????????????????behavior.HttpGetEnabled?=?true;?
  • ????????????????????behavior.HttpGetUrl?=?new?Uri("http://127.0.0.1:9999/FileService/metadata");?
  • ????????????????????host.Description.Behaviors.Add(behavior);?
  • ????????????????}?
  • ????????????????host.Opened?+=?delegate?
  • ?????????????????{?
  • ?????????????????????Console.WriteLine("FileService已經啟動,按任意鍵終止服務!");?
  • ?????????????????};?
  • ????????????????host.Open();?
  • ????????????????Console.Read();?
  • ????????????}?
  • ????????}?
  • ????}?
  • ?
  • ????class?FileImp?:?IFile?
  • ????{?
  • ????????static?byte[]?_fileContent?=?new?byte[1024?*?8];?
  • ?
  • ????????public?byte[]?GetFile(string?fileName)?
  • ????????{?
  • ????????????int?loginID?=?OperationContext.Current.IncomingMessageHeaders.GetHeader<int>("LoginID",?string.Empty);?
  • ????????????Console.WriteLine(string.Format("調用者ID:{0}",?loginID));?
  • ????????????return?_fileContent;?
  • ????????}?
  • ????}?
  • ? ? 2、客戶端代碼,循環調用GetFile操作,在調用前給消息頭添加一些登錄信息。另外為了避免垃圾回收機制執行的不確定性對內存增長的干擾,在每次調用完畢后,強制啟動垃圾回收機制,對所有代進行垃圾回收,確保增長的內存都是可到達,無法對其進行回收。

  • class?Program?
  • ????{?
  • ????????static?void?Main(string[]?args)?
  • ????????{?
  • ????????????int?callCount?=?0;?
  • ????????????int?loginID?=?0;?
  • ????????????while?(true)?
  • ????????????{?
  • ????????????????using?(ChannelFactory<IFile>?channelFactory?=?
  • ????????????????????new?ChannelFactory<IFile>(new?WSHttpBinding(),?"http://127.0.0.1:9999/FileService"))?
  • ????????????????{?
  • ????????????????????IFile?fileProxy?=?channelFactory.CreateChannel();?
  • ????????????????????using?(fileProxy?as?IDisposable)?
  • ????????????????????{?
  • ????????????????????????//OperationContext.Current?=?new?OperationContext(fileProxy?as?IContextChannel);?
  • ????????????????????????OperationContextScope?scope?=?new?OperationContextScope(fileProxy?as?IContextChannel);?
  • ????????????????????????var?loginIDHeadInfo?=?MessageHeader.CreateHeader("LoginID",?string.Empty,?++loginID);?
  • ????????????????????????OperationContext.Current.OutgoingMessageHeaders.Add(loginIDHeadInfo);?
  • ????????????????????????byte[]?fileContent?=?fileProxy.GetFile(string.Empty);?
  • ????????????????????}?
  • ????????????????}?
  • ????????????????GC.Collect();//強制啟動垃圾回收?
  • ????????????????Console.WriteLine(string.Format("調用次數:{0}",?++callCount));?
  • ????????????}?
  • ????????}?
  • ????}?
  • 二、分析排查

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

    ? ? 從上圖可看到OperationContextScope對象占用了98%的內存,當前OperationContextScope對象持有256個 OperationContextScope對象的引用,這些OperationContextScope對象總共持有258個 OperationContext的引用,每個OperationContext對象持有客戶端代理的相關對象引用,導致每個客戶端代理產生的內存在使用 完畢后都無法得到釋放。

    三、問題解決

    ? ??OperationContextScope類主要作用是創建一個塊,其中 OperationContext 對象在范圍之內。也就是說創建一個基于OperationContext?的上下文范圍,在這范圍內共享一個相同的OperationContext對 象。這種上下文的特性是支持嵌套的,即一個大的上下文范圍內可以有若干個小的上下文范圍,而且不會造成相互不干擾。所以如果沒顯式調用該對象的 Dispose方法結束當前上下文恢復前一上下文,再利用OperationContextScope類創建新的上下文,就會一直嵌套下去。所以在這里應 該要顯式調用Dispose方法結束當前OperationContextScope上下文范圍,這樣可以解決內存持續增長的問題了。

  • class?Program?
  • ????{?
  • ????????static?void?Main(string[]?args)?
  • ????????{?
  • ????????????int?callCount?=?0;?
  • ????????????int?loginID?=?0;?
  • ????????????while?(true)?
  • ????????????{?
  • ????????????????using?(ChannelFactory<IFile>?channelFactory?=?
  • ????????????????????new?ChannelFactory<IFile>(new?WSHttpBinding(),?"http://127.0.0.1:9999/FileService"))?
  • ????????????????{?
  • ????????????????????IFile?fileProxy?=?channelFactory.CreateChannel();?
  • ????????????????????using?(fileProxy?as?IDisposable)?
  • ????????????????????{?
  • ????????????????????????//OperationContext.Current?=?new?OperationContext(fileProxy?as?IContextChannel);?
  • ????????????????????????using?(OperationContextScope?scope?=?new?OperationContextScope(fileProxy?as?IContextChannel))?
  • ????????????????????????{?
  • ????????????????????????????var?loginIDHeadInfo?=?MessageHeader.CreateHeader("LoginID",?string.Empty,?++loginID);?
  • ????????????????????????????OperationContext.Current.OutgoingMessageHeaders.Add(loginIDHeadInfo);?
  • ????????????????????????}?
  • ????????????????????????byte[]?fileContent?=?fileProxy.GetFile(string.Empty);?
  • ????????????????????}?
  • ????????????????}?
  • ????????????????GC.Collect();//強制啟動垃圾回收?
  • ????????????????Console.WriteLine(string.Format("調用次數:{0}",?++callCount));?
  • ????????????}?
  • ????????}?
  • ????}?
  • ?四、問題根源

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

  • public?sealed?class?OperationContextScope?:?IDisposable?
  • ????{?
  • ????????[ThreadStatic]?
  • ????????static?OperationContextScope?currentScope;?
  • ??
  • ????????OperationContext?currentContext;?
  • ????????bool?disposed;?
  • ????????readonly?OperationContext?originalContext?=?OperationContext.Current;?
  • ????????readonly?OperationContextScope?originalScope?=?OperationContextScope.currentScope;?
  • ????????readonly?Thread?thread?=?Thread.CurrentThread;?
  • ??
  • ????????public?OperationContextScope(IContextChannel?channel)?
  • ????????{?
  • ????????????this.PushContext(new?OperationContext(channel));?
  • ????????}?
  • ??
  • ????????public?OperationContextScope(OperationContext?context)?
  • ????????{?
  • ????????????this.PushContext(context);?
  • ????????}?
  • ??
  • ????????public?void?Dispose()?
  • ????????{?
  • ????????????if?(!this.disposed)?
  • ????????????{?
  • ????????????????this.disposed?=?true;?
  • ????????????????this.PopContext();?
  • ????????????}?
  • ????????}?
  • ??
  • ????????void?PushContext(OperationContext?context)?
  • ????????{?
  • ????????????this.currentContext?=?context;?
  • ????????????OperationContextScope.currentScope?=?this;?
  • ????????????OperationContext.Current?=?this.currentContext;?
  • ????????}?
  • ??
  • ????????void?PopContext()?
  • ????????{?
  • ????????????if?(this.thread?!=?Thread.CurrentThread)?
  • ????????????????throw?DiagnosticUtility.ExceptionUtility.ThrowHelperError(new?InvalidOperationException(SR.GetString(SR.SFxInvalidContextScopeThread0)));?
  • ??
  • ????????????if?(OperationContextScope.currentScope?!=?this)?
  • ????????????????throw?DiagnosticUtility.ExceptionUtility.ThrowHelperError(new?InvalidOperationException(SR.GetString(SR.SFxInterleavedContextScopes0)));?
  • ??
  • ????????????if?(OperationContext.Current?!=?this.currentContext)?
  • ????????????????throw?DiagnosticUtility.ExceptionUtility.ThrowHelperError(new?InvalidOperationException(SR.GetString(SR.SFxContextModifiedInsideScope0)));?
  • ??
  • ????????????OperationContextScope.currentScope?=?this.originalScope;?
  • ????????????OperationContext.Current?=?this.originalContext;?
  • ??
  • ????????????if?(this.currentContext?!=?null)?
  • ????????????????this.currentContext.SetClientReply(null,?false);?
  • ????????}?
  • ????}?
  • ? ? 當前的上下文對象由線程唯一的靜態字段currentScope持有,其實例字段originalScope保持前一上下文對象的引用,如果使用完畢后不 結束當前上下文范圍,就會一直嵌套下去,導致所有OperationContext對象都保持可到達,垃圾回收機制無法進行回收,從而使得內存持續增長, 直到內存溢出。

    ? ?

    五、總結

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


    作者:豬樂乎

    來源:51CTO

    總結

    以上是生活随笔為你收集整理的NET内存持续增长问题排查的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。