文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

NET中怎么排查内存持续增长问题

2023-06-17 06:11

关注

这期内容当中小编将会给大家带来有关NET中怎么排查内存持续增长问题,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

    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帮助我们的性能测试人员以及研发人员,找到内存没有及时回收,占着内存不释放的方法。监测客户端程序运行的结果如下:

NET中怎么排查内存持续增长问题

     从上图可看到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对象都保持可到达,垃圾回收机制无法进行回收,从而使得内存持续增长, 直到内存溢出。

    NET中怎么排查内存持续增长问题

上述就是小编为大家分享的NET中怎么排查内存持续增长问题了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯