none
如何降低Windows服务的内存消耗? RRS feed

  • 问题

  • 我编写了一个Windows服务程序,作用很简单,就是每隔一段时间像电脑的一个Txt写一点东西,编译出来的程序才7K,但是当我安装完服务运行的时候,开始就占用了15M内存,然后不断飚升,最后停止到了35M,请问各位高人,为什么会占用这么大的呢?如何进行优化的哦?是不是我的源码里面有问题呢?下面提供源码。

     protected override void OnStart(string[] args)
            {
                System.Timers.Timer aTimer = new System.Timers.Timer();
                aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
                aTimer.Interval = 300000;

    /五分钟执行一次
                aTimer.Enabled = true;
                 GC.KeepAlive(aTimer);
            }
            public static void OnTimedEvent(object source, ElapsedEventArgs e)
            {
                string tempstring = "";
                System.DateTime currentTime = new System.DateTime();
                currentTime = System.DateTime.Now;
                string TimeNow = Convert.ToString(currentTime);
                tempstring = tempstring + "现在的时间是:" + TimeNow + "\r\n";
                System.IO.File.AppendAllText(C:\test.txt, tempstring, Encoding.Unicode);
            }

    2010年8月11日 9:09

答案

  • 还有,根据 Jeffery Richter 的建议,尽量不要使用除 System.Threading.Timer 之外的其他 Timer (如 System.Timers.Timer 以及 System.Windows.Forms.Timer),第一个 System.Threading.Timer 是一个非常轻量级的,Primitive 的实现,后两者,一个用于针对事件的异步模型 (Event-based Asynchronous Programming Model, EAPM),一个用于 EAPM 以及 Windows Forms 组件设计器。后两者都引入了 Event,而实现 Event 会产生额外的类型成员,以及内存消耗。并且,后面两个 Timer 的大多数成员对于此程序无用。

    15 MB 到 35 MB 的内存占用完全正常。如果您希望降低自己程序的内存消耗 (Working set of your process),您需要调整一些 GC 设置,如禁用 Server GC。但我不建议您这样做,CLR 默认为 Server 应用程序启用 Server GC,虽然内存消耗鞥大胆程序性能提高了。

    如果将您的程序运行在 x64 的计算机上,以及多核 CPU 的机器上,您的进程的 Working Set 可能会降低 50%。达到 7 到 17 MB。

    假设现在程序运行在 x86 上,这 大约 30 MB 的内存包括:

    - CLR Hosting 的 COM Stub (mscoree.dll) 和 CLR 工作 COM 进程 (mscorwks.exe (2.0) / clr.dll (4.0));这大概需要 2 到 3 MB。
    - 加载的引用程序集。System.dll, System.Core.dll, Microsoft.CSharp.dll 等,每一个大概需要 0.5 MB 到 2 MB 不等,共计约 5 MB。mscorlib.dll 是共享加载的,不算。
    - 程序的进程信息上下文,不到 1 MB。
    - 程序的线程。每一个线程 1 到 2 MB (包括线程环境块 Threading Environment Block, TEB,执行上下文 Thread Context,用户模式堆栈 User Mode Stack 和内核模式堆栈 Kernel Mode Stack),您的程序至少应具备 3 个线程:主线程,CLR,还有至少一个线程池活动线程 (CLR 会根据 CPU 的数量决定初始线程池的线程数量),这样,至少花费 6 MB。
    - 保留的虚拟内存地址块。
    - 其他 Windows 需要的信息,如 Windows Service 的控制信息等。

    所以,30 MB 应该完全属于正常。

    从这个案例可以看到,尽可能的使用线程池,而非独立创建线程。这也是 .NET 多线程设计中的第一准侧。


    Mark Zhou
    2010年11月2日 8:40
  • 严格的说,楼主的代码,确实存在某些不必要的步骤。

    首先,currentTime变量没有必要先new出实例,因为你new出的实例马上就被下一行的“Now”所替换,这导致第一次的实例创建显得无用。其次,对于tempstring采用了多次“+”操作,这可以考虑使用string.Format方法来替换。

    请考虑使用以下代码替换您的方法体,不过我怀疑这是否能为您的代码的内存消耗带来明显降低。楼主可以使用windows性能计数器或者其他工具分析代码的执行情况。

    string currentTime = string.Format("现在的时间是:{0}\r\n", System.DateTime.Now);
    System.IO.File.AppendAllText("C:\test.txt", currentTime, Encoding.Unicode);
    


    理解的越多,需要记忆的就越少
    2010年11月2日 6:39
    版主

全部回复

  • 这段代码没什么太大问题,唯一一个比较可疑的是 GC.KeepAlive 的使用,aTimer 实际上应该做为一个类字段更适合,不知道你为什么放到 OnStart 里面作为一个局部变量?
    问题要简单,错误须详细@错误/异常/堆栈信息+操作系统+软件版本+all the context of the issue Hope Helpful | http://www.leoworks.net
    2010年8月11日 9:18
  • 从代码看没有什么太大的可优化空间。可以把对 currentTime 的两行代码合并成 string timeNow = DateTime.Now.ToString(),这样少产生一个局部变量。

    内存在 15M 左右是正常的,CLR 将引用的类型加载进入程序的 AppDomain 中,我觉得这个程序的内存消耗应该不大于 40 MB 算是正常。Timer 会占用一些 System Idle 时间和内存,也会对数据有一些影响。

    最后,为什么会用到 GC.KeepAlive()?


    Mark Zhou
    2010年8月11日 9:22
  • 你把你的代码都注释了,再看看占用内存量。
    2010年8月11日 12:28
  • 这个程序原本是由一个控制台程序转过来的,我看到一篇文章说,因为控制台程序如果不用KeepAlive会自动结束,这个让他结束都保持timer一直在工作,所以用了这个,请问如果让他一直工作,不用KeepAlive应该怎么写呢?
    2010年8月11日 12:38
  • 严格的说,楼主的代码,确实存在某些不必要的步骤。

    首先,currentTime变量没有必要先new出实例,因为你new出的实例马上就被下一行的“Now”所替换,这导致第一次的实例创建显得无用。其次,对于tempstring采用了多次“+”操作,这可以考虑使用string.Format方法来替换。

    请考虑使用以下代码替换您的方法体,不过我怀疑这是否能为您的代码的内存消耗带来明显降低。楼主可以使用windows性能计数器或者其他工具分析代码的执行情况。

    string currentTime = string.Format("现在的时间是:{0}\r\n", System.DateTime.Now);
    System.IO.File.AppendAllText("C:\test.txt", currentTime, Encoding.Unicode);
    


    理解的越多,需要记忆的就越少
    2010年11月2日 6:39
    版主
  • 还有,根据 Jeffery Richter 的建议,尽量不要使用除 System.Threading.Timer 之外的其他 Timer (如 System.Timers.Timer 以及 System.Windows.Forms.Timer),第一个 System.Threading.Timer 是一个非常轻量级的,Primitive 的实现,后两者,一个用于针对事件的异步模型 (Event-based Asynchronous Programming Model, EAPM),一个用于 EAPM 以及 Windows Forms 组件设计器。后两者都引入了 Event,而实现 Event 会产生额外的类型成员,以及内存消耗。并且,后面两个 Timer 的大多数成员对于此程序无用。

    15 MB 到 35 MB 的内存占用完全正常。如果您希望降低自己程序的内存消耗 (Working set of your process),您需要调整一些 GC 设置,如禁用 Server GC。但我不建议您这样做,CLR 默认为 Server 应用程序启用 Server GC,虽然内存消耗鞥大胆程序性能提高了。

    如果将您的程序运行在 x64 的计算机上,以及多核 CPU 的机器上,您的进程的 Working Set 可能会降低 50%。达到 7 到 17 MB。

    假设现在程序运行在 x86 上,这 大约 30 MB 的内存包括:

    - CLR Hosting 的 COM Stub (mscoree.dll) 和 CLR 工作 COM 进程 (mscorwks.exe (2.0) / clr.dll (4.0));这大概需要 2 到 3 MB。
    - 加载的引用程序集。System.dll, System.Core.dll, Microsoft.CSharp.dll 等,每一个大概需要 0.5 MB 到 2 MB 不等,共计约 5 MB。mscorlib.dll 是共享加载的,不算。
    - 程序的进程信息上下文,不到 1 MB。
    - 程序的线程。每一个线程 1 到 2 MB (包括线程环境块 Threading Environment Block, TEB,执行上下文 Thread Context,用户模式堆栈 User Mode Stack 和内核模式堆栈 Kernel Mode Stack),您的程序至少应具备 3 个线程:主线程,CLR,还有至少一个线程池活动线程 (CLR 会根据 CPU 的数量决定初始线程池的线程数量),这样,至少花费 6 MB。
    - 保留的虚拟内存地址块。
    - 其他 Windows 需要的信息,如 Windows Service 的控制信息等。

    所以,30 MB 应该完全属于正常。

    从这个案例可以看到,尽可能的使用线程池,而非独立创建线程。这也是 .NET 多线程设计中的第一准侧。


    Mark Zhou
    2010年11月2日 8:40