小型应用程序的内存使用率高

3

我正在构建一个非常简单的基于事件的代理监视器,根据网络位置是否可用来禁用代理设置。

问题在于该应用程序只有10KB左右的大小,并且界面非常简单,但它却使用了10MB的内存。

代码非常简单:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.NetworkInformation;
using Microsoft.Win32;

namespace WCSProxyMonitor
{
    class _Application : ApplicationContext
    {
        private NotifyIcon NotificationIcon = new NotifyIcon();
        private string IPAdressToCheck = "10.222.62.5";

        public _Application(string[] args)
        {
            if (args.Length > 0) 
            {
                try
                {
                    IPAddress.Parse(args[0]); //?FormatException
                    this.IPAdressToCheck = args[0];
                }
                catch (Exception) 
                {}
            }

            this.enableGUIAspects();
            this.buildNotificationContextmenu();
            this.startListening();
        }

        private void startListening() 
        {
            NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(networkChangeListener);
        }

        public void networkChangeListener(object sender, EventArgs e)
        {
            //foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
            //{
                //IPInterfaceProperties IPInterfaceProperties = nic.GetIPProperties();
            //}

            //Attempt to ping the domain!
            PingOptions PingOptions = new PingOptions(128, true);
            Ping ping = new Ping();

            //empty buffer
            byte[] Packet = new byte[32];

            //Send
            PingReply PingReply = ping.Send(IPAddress.Parse(this.IPAdressToCheck), 1000, Packet, PingOptions);

            //Get the registry object ready.
            using (RegistryKey RegistryObject = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true)) 
            {
                if (PingReply.Status == IPStatus.Success)
                {
                    this.NotificationIcon.ShowBalloonTip(3000, "Proxy Status", "proxy settings have been enabled", ToolTipIcon.Info);
                    RegistryObject.SetValue("ProxyEnable", 1, RegistryValueKind.DWord);
                }
                else
                {
                    this.NotificationIcon.ShowBalloonTip(3000, "Proxy Status", "proxy settings have been disabled", ToolTipIcon.Info);
                    RegistryObject.SetValue("ProxyEnable", 0, RegistryValueKind.DWord);
                }
            }
        }

        private void enableGUIAspects()
        {
            this.NotificationIcon.Icon = Resources.proxyicon;
            this.NotificationIcon.Visible = true;
        }

        private void buildNotificationContextmenu()
        {
            this.NotificationIcon.ContextMenu = new ContextMenu();
            this.NotificationIcon.Text = "Monitoring for " + this.IPAdressToCheck;

            //Exit comes first:
           this.NotificationIcon.ContextMenu.MenuItems.Add(new MenuItem("Exit",this.ExitApplication));
        }

        public void ExitApplication(object Sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}

我的问题是:

  • 一个基于C#构建的应用程序是否正常使用这么多内存?
  • 我该怎么做来减少内存的使用量?

该应用程序是在.NET 4.0框架上构建的。

谢谢!


1
你的代码库可能有10k,但你忘记了你正在使用的框架,它并不小,并且也被加载了。 - Lloyd
该应用程序在10K的情况下编译,我已将其从发布文件夹迁移到桌面,我可以理解大约2~3 MB的内存占用,但是对于这样一个小应用来说,10MB是过多的资源:( - RobertPitt
3
它还为您的应用程序预留了一些RAM,以便其可以扩展,因此保留了10MB但可能并未实际使用。 - Adam Houldsworth
@Robert,你对内存使用有什么限制?如果你的内存如此短缺,为什么选择基于VM的编程环境? - David Heffernan
1
很遺憾,這是我唯一熟悉的語言來完成這項任務。 - RobertPitt
显示剩余3条评论
4个回答

7
它并未使用接近10MB的RAM,而是使用了10MB的地址空间。地址空间使用与RAM几乎没有任何关系。
当您加载.NET框架时,所有代码的空间都被保留在您的地址空间中,而不是加载到RAM中。代码将按需以4KB为单位的“页面”加载到RAM中,但这些页面的空间必须在地址空间中被保留,以便进程确保有一个地址空间中可能需要的所有代码的空间。
此外,当每个页面加载到RAM中时,如果同时运行两个.NET应用程序,则它们共享该页面的RAM。内存管理器负责确保共享代码页面仅加载一次到RAM中,即使它们在一千个不同的地址空间中也是如此。
如果您要测量内存使用情况,您需要了解现代操作系统中内存的工作方式。自286天以来,情况已经发生了变化。
请参见以下相关问题: Is 2 GB really my maximum? 以及我关于该主题的文章,对内存实际工作方式进行简要介绍。 http://blogs.msdn.com/b/ericlippert/archive/2009/06/08/out-of-memory-does-not-refer-to-physical-memory.aspx

1
+1,我完全同意需要了解内存管理器的工作原理,我知道你所说的一切,只是不太清楚细节,你为我澄清了一些问题,我很感激。 - RobertPitt
似乎保留地址空间并不会花费任何费用,因为地址空间仅供该进程使用;那么为什么不在启动进程时尽可能多地保留地址空间呢?并且为什么不让操作系统在启动进程时自动保留2GB(或者64位机器的限制)的地址空间呢? - configurator
@configurator:记住,进程是由DLL共享的。假设加载的第一个DLL保留了整个地址空间。那么你如何加载第二个DLL呢?它的代码无处可去! - Eric Lippert
这就说明了问题 - 我原本以为地址空间是针对整个进程而预留的,而不是针对特定的DLL。 - configurator

1

如果您刚开始运行应用程序并检查内存使用量,数字可能很高。当应用程序启动时,.Net应用程序会预加载约10 MB的内存。在您的应用程序运行一段时间后,您应该看到内存使用量下降。此外,仅因为您在任务管理器中看到应用程序使用了特定数量的内存,并不意味着它正在使用该数量。.Net还可以共享某些组件的内存以及预分配内存。如果您真的担心,请为您的应用程序获取一个真正的分析器。


1
顺便提一下,每个线程大约保留1MB。大多数.NET应用程序有3个或更多线程。 - Matthew Whited
我使用了一个配置文件,唯一的高峰是从png转换为ico用于NotifyIcon,我手动将其转换为ico文件并更改了代码,但仍然没有改变任何东西,这让我相信你是正确的,我会让应用程序运行一段时间来观察发生了什么。另外,我只有主线程(我相信),我知道线程很昂贵,所以我将其与GUI线程绑定在一起,也许我会添加一个用于网络监控的线程。 - RobertPitt
.Net 会为你创建一些线程。如果你检查一下,你至少会看到主线程和一个 GC 线程。你可能还会看到另一个处理事件等的线程。 - Matthew Whited

1

你的应用程序本身很小,但它引用了.NET框架中的类。它们也需要加载到内存中。当你使用Sysinternals的Process Explorer时,你可以看到加载了哪些dll,并且如果你选择更多的列,还可以看到它们使用了多少内存。这应该有助于解释一些内存占用的原因,其他答案中描述的原因仍然可能有效。

你可以尝试使用GC.Collect()来查看之后使用了多少内存,但不建议在生产代码中操纵GC。

祝好,GJ


感谢您的回答,经过使用您推荐的软件,实际上它下面没有任何库,但它是资源管理器的子进程。 - RobertPitt
这就是进程之间的关联。当您按下CTRL-D时,下方的面板将显示所选进程加载的所有Dll。然后,在下方网格的列上单击鼠标右键以添加“WS total bytes”,该字节提供了所有已加载dll的内存占用情况。不确定它们是否与其他正在运行的程序共享,可能它们并非专门为您的进程加载,这就是我的知识止步之处。 - gjvdkamp

0

是的,这对于 C# 应用程序来说很正常,启动 CLR 需要一些努力。 至于减少此操作,加载的 DLL 越少越好,因此请检查您可以删除哪些引用。

例如,我看到您正在导入 Linq,但在快速扫描代码时没有看到任何内容,请问您能否移除它并减少项目所依赖的 DLL 数量。

我还看到您正在使用 Windows Forms,对于使用表单的任何应用程序来说,10M 并不算大。


1
仅仅因为你有一个引用和一个using导入语句,并不意味着程序集已经被加载。如果你没有使用System.Core(LINQ和其他一些东西),那么这个程序集就不会被加载到内存中。 - Matthew Whited
我的应用程序中唯一使用的实体是一个 NotifiyIcon,没有任何窗口。@Matthew Whited,我同意它们被称为“引用”,因为它们只是“被引用”。 - RobertPitt
@RobertPitt,如果你正在使用System.Windows.Forms中的任何代码,那么你的应用程序将会使用更多的程序集。 - Chris O

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接