公司里的一个程序,经过了N个人的手后发现上了生产内存会一直涨,直到物理内存几乎被占用完毕后突然就下降下来(估计是GC给释放了),然后再一直涨。这个程序主要是对字符串进行处理,困扰了我好几天,本机测试内存也有增长的趋势,但是不大明显,上生产是2、3分钟内存就上G,没找到究竟哪里的问题。
从网上搜了些资料,看到可以让.NET立即释放内存,本地简单测试了一下,真的管用,但是没敢用,不知道对于多线程的程序,当一个线程显示释放内存时会不会影响到其他线程,而且我是做金融行业程序的,程序万一出问题就是事故,所以请教一下各位高人,这段代码是否会影响程序的稳定性?
释放内存的代码如下:
[DllImport("kernel32.dll")]
        public static extern bool SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);public static void GarbageCollect()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }        public static void FlushMemory()
        {
            GarbageCollect();            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                Win32.SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
            }
        }NET里面还有一个类可以达到这个效果
   System.Diagnostics.Process.GetCurrentProcess().MinWorkingSet = new System.IntPtr(5);采用定时释放+窗体打开的时候,效果很好,估计程序占的内存会在20以下

解决方案 »

  1.   

    System.Diagnostics.Process.GetCurrentProcess().MinWorkingSet这个确实有可能让程序跑得更慢
      

  2.   

     GC.Collect();
    只是强制释放没有被使用的资源,在使用的是不会被删除的,这是托管代码不是C++代码是不会有什么问题的,有问题的是可能在释放的时候,会占用比较高的CPU资源,
      

  3.   

    GC.Collect();
    GC.WaitForPendingFinalizers();只要你没有内存泄露, 用这个基本上能解决, 就是在运行某段代码后调用一次。
    在32位windows一个进程允许的最大内存可能是1.5G左右, 超了很容易OOM
      

  4.   


    加两G内存自然不是问题,可是不知道大家是否考虑过,WIN32 系统中,内存寻址最多到3G,再多加了操作系统也用不上啊,现在系统已经是插了两条2G内存,但有效的内存应当只有3G,这个服务器上不止这一个程序,还有SQL server和其它应用。仅SQL server 就得占1.5G,所以我说的这个程序每次内存用到1.5G以下(内存剩余个2~3百M,甚至几十M)的时候看似才会被.NET回收。
    我担心系统内存都被他用了会影响其他的程序,而且也发生过程序内存溢出的异常。
      

  5.   

    为什么不调试下看看是什么地方出问题了,什么对象没有释放掉?
    内存泄露有很多工具撒,推荐使用windbg,一个!dumpheap -stat就可以查看你所有的托管对象占用的内存情况。1. 加载进程和SOS扩展:
           a. F6或者使用菜单Files –> Attach to a process…来Attach一个托管进程        b.使用命令.loadby sos mscorwks来加载SOS扩展(注意:.Net1.1时代的SOS扩展已经自带于下载安装的WinDBG中,从.Net2.0以后,SOS扩展已经自带到.Net框架中:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\SOS.dll,为了不至于引起混淆,最好的方法就是使用前面的loadby调试器元命令来让WinDBG自己决定加载什么版本的SOS。 mscorwks表示.Net框架的工作站版本,现在我们安装的.Net Framework都是该版本)        c.加载SOS后,使用命令.chain来查看调试链中是否已经成功包含SOS扩展,如下图的WinDBG输出就表示已经成功的加载了SOS: 2. 查看进程加载的模块并加载调试符号:
           Attach到进程并顺利加载SOS扩展后,我们可以使用lm命令来查看当前进程已经加载的模块,WinDBG会列出一个模块加载列表,这个列表非常有用,我们后面设置断点,查看方法表和IL以及汇编代码都需要使用到相应的Module Name。另外对于我们常用的情况:调试IISHost的进程,由于进程的名字都是w3wp,我们需要区分出哪个进程才是我们想要调试的Application,这个时候我们就可以通过lm命令列出的加载模块列表来判断: 接下来我们就可以根据Module Name来给对应的Module加载调试符号库,由于在使用WinDBG进行调试的时候大多数时候我们都会查看汇编代码,所以加载调试符号对于辅助我们更好的看懂汇编代码和相关的变量命名是必须的,WinDBG默认会帮我们加载相关Module的调试符号,我们也可以使用ld [ModuleName]来显示的加载某一个Module的调试符号,例如ld BasicDebugDemo指定加载BasicDebugDemo这个Module的调试符号。 3. 准备就绪,开始调试:
    经过1,2两个步骤的准备,我们的调试环境已经准备好了,接下来就可以开始调试了,下面介绍一些重要的和常用的调试命令:        1) 根据调试符号库的信息查看类或者方法在.Net执行引擎中的具体元信息,包括类的方法表地址,类在运行时的元信息(父类元信息地址,虚方法表地址等很多有用信息),这些信息是我们接下来调试的基础!使用命令!name2ee [ModuleName] [ClassName or MehodName]来进行查看,例如:!name2ee BasicDebugDemo BasicDebugDemo.Program: 我们也可以使用!name2ee命令直接查看某个类方法的元信息: 2) 有了上面得到的一个类的方法表的入口地址后,我们就可以使用!dumpmt [-md] [MethodTabel Address]来查看这个类中每一个方法的具体运行时信息(加带md选项表示我们要查询每一个方法声明的入口地址),例如我们利用上面得到的00993030这个地址,!dumpmt –md 00993030: 3) 另外我们通过上面的!name2ee命令除了拿到MehodTable的地址外,还拿到了Program这个Class的运行时元信息地址EEClass Address,所以我们可以通过!dumpclass [Class Address]来查看这个类中的具体内容(比如其中的静态变量的地址等): 4) 上面提到了一个很重要的信息MehodDesc,得到这个MethodDesc有什么用呢?首先我们可以使用!dumpil [MethodDesc]来查看其编译后的IL代码: 5) 另外如果这个方法已经被JIT了,那么我们调用!dumpmd [MethodDesc]会得到下面的信息: 接下来我们就可以使用!u [LocalCodeAddr]来查看生成的本地汇编代码: 6) 上面能获取的元信息我们基本上都获取了,有了这些元信息(主要是代码地址信息)我们要下断点就很简单了,我们有很多种方式来给我们的代码的指定位置加上断点P     a.首先如果调试符号加载成功的话,我们可以直接使用很直观的原始方法命名来给指定的方法入口处加上断点:!bpmd [ModuleName] [MethodName],例如!bpmd BasicDebugDemo BasicDebugDemo.Program.CreateFooObject     b.如果我们有MethodDesc,也可以直接使用!bpmd -md [MethodDesc]来给方法入口处加上断点,例如!bpmd -md 0099301c    c.我们上面看到了本地汇编代码,所以我们也可以使用bp [CodeAddr]来给某个方法内部的某行代码加上断点。    d.我们可以使用bl命令来列出所有已加载的断点,也可以使用bc [BreakPoint ID]命令来删除指定ID的断点(用bl命令列出的断点列表种有各个断点的ID),或者使用bc *来删除所有的断点。     e.断点加载成功后,WinDBG会给出类似下面这样的提示:          Found 1 methods... MethodDesc = 00993028 Adding pending breakpoints...     f.当我们的断点命中后,我们仍然可以像普通调试一样来使用F10 Step Out单步执行,使用F11来Step Into单步执行。注意:这里的单步执行,都是指单步汇编代码,而不是我们所写的代码。 7) 在命中断点后我们就可以通过查看类命令来查看栈和堆中的变量的值以及当前的调用堆栈:   a.使用!clrstack可以查看当前的条用堆栈,使用!clrstack –l可以查看当前调用堆栈以及其上的局部变量和值,使用!clrstack –p可以查看当前调用堆栈上的参数变量以及值,使用!clrstack –a可以查看当前堆栈上所有局部变量和参数变量以及值(格式为StackAddress =  StackValue)。!clrstack命令只会显示托管代码的调用堆栈,如果想查看完整的调用堆栈可以使用!dumpstack命令: b.使用!dso命令可以查看堆栈上的所有对象   c.使用!do [ObjectAddress]查看指定对象的具体内容: 8) 除了通过堆栈查看栈上的变量外,我们还可以直接通过!dumpheap来查看目前堆中的所有对象,但是由于一般情况下堆中存在的变量会非常的多(包含.Net框架里的很多预定义对象),所以直接使用!dumpheap得到的结果一般我们很难查看。大多数情况下,我们需要查看的是堆中指定类型的对象,所以我们可以使用!dumpheap –type [ClassName]来查看指定类型的对象: 9)如何检查内存泄漏?WinDBG SOS中有一个很有用的命令!GCRoot [ObjectAddress]可以帮助我们查看指定对象的引用情况,这个信息可以很好的帮助我们分析那些本应该没有引用但却一直还存在有效引用的对象,由此发现我们代码中潜在的内存泄漏,同时我们也可以观察到哪些对象是目前没有引用了,但是GC还没有回收的: 4. 总结:
    1) WinDBG不是专门用于调试.Net程序的工具,它更偏向于底层,可用于内核和驱动调试。进行普通的.Net程序调试还是使用微软专为.Net开发的调试工具MDBG更方便一些。但是WinDBG能看到更多的底层信息,对于某些特别疑难的问题调试有所帮助,例如内存泄漏等问题。 2) SOS扩展命令中最有用的命令是!help命令J,使用该命令可以列出所有可用的SOS扩展命令列表,使用!help [SOSCommandName]可以查看每一个具体扩展命名的详细使用说明,例如!help dumpheap就可以查看!dumpheap这个扩展命名的具体使用方法。多多利用!help命名可以很快上手SOS。 3) WinDBG本身的资料可以参考 张银奎 先生的《软件调试》一书,另外在互联网上也有非常多的WinDBG资料。 
      

  6.   

    学习一下,我也遇到这样的问题了,是在wince下