WPF内存泄漏

6
我有一个简单的WPF应用程序,在主窗口中我有一个堆栈面板和2个按钮。第一个按钮添加了100个我的用户控件(没有任何数据绑定、事件、位图),第二个按钮从面板中删除所有用户控件并调用GC.Collect()。然而存在一些问题: 1.当我第一次点击“删除”按钮后,并不是所有的内存都被释放,我必须点击几次才能释放更多的内存。 2.5-10分钟后内存才被释放,但还有几兆字节没有被释放。
例如,当我的应用程序启动时,它占用大约22MB的内存。 当我添加500个控件时,它需要大约60MB的内存。 当我第一次点击“删除”按钮时,它需要大约55MB的内存(我等了一段时间,内存没有被释放)。 我点击几次后,内存降至25MB, 我不理解这一点,我是WPF的新手,也许我错过了什么。 我希望能立即释放内存。
<Window x:Class="WpfApplication10.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="385" Width="553">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="25" />
        <RowDefinition Height="240*" />
        <RowDefinition Height="25" />
    </Grid.RowDefinitions>
    <Grid 
            Name="border1" 
            Grid.Row="1"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch" >
        <ScrollViewer VerticalAlignment="Stretch"
                      Name="scrollViewer1" 
                      HorizontalAlignment="Stretch">
            <StackPanel 
                Margin="3,3,3,3"
                Background="Transparent"
                VerticalAlignment="Stretch"
                Name="activityStackPanel"
                HorizontalAlignment="Stretch">
            </StackPanel>
        </ScrollViewer>
    </Grid>
    <Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,0,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
    <Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="141,0,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="button2_Click" />
    <Label Content="Label" Grid.RowSpan="2" Height="28" HorizontalAlignment="Left" Margin="34,0,0,0" Name="label1" VerticalAlignment="Top" />
</Grid>

namespace WpfApplication10
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            int N = 100;
            //var r = new ActivityStatisticItem("111", "222", DateTime.Now, "333", 1);
            for (int i = 0; i < N; i++)
            {
                activityStackPanel.Children.Add(new UserControl1());
            }

            label1.Content = activityStackPanel.Children.Count;
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
           activityStackPanel.Children.Clear();

            label1.Content = activityStackPanel.Children.Count;

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }
}
<UserControl x:Class="WpfApplication10.UserControl1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         Background="Transparent"
         Margin="0,2,0,2"
         MinHeight="80"
         MinWidth="130"
         MaxHeight="80">
<Grid Width="441">
    <Grid.RowDefinitions>
        <RowDefinition Height="40" Name="rowTop" />
        <RowDefinition Height="40" Name="rowBottom"/>
    </Grid.RowDefinitions>
    <Border BorderBrush="Gray" 
            BorderThickness="1" 
            HorizontalAlignment="Stretch" 
            Background="LightGreen"
            Name="contactPanel" 
            CornerRadius="3,3,3,3"
            VerticalAlignment="Stretch" Panel.ZIndex="1" >
        <Grid
            VerticalAlignment="Stretch" 
            Name="grid1" 
            Margin="3,0,3,0"
            HorizontalAlignment="Stretch">

            <Label Content="Contact" Height="15" HorizontalAlignment="Left" Margin="15,3,0,0" Name="headerLabel" Padding="0" VerticalAlignment="Top" FontSize="10" FontWeight="DemiBold"/>
            <Label Content="00/00/0000 00:00:00" Height="15" HorizontalAlignment="Left" Margin="13,18,0,0" Name="timeLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="100" FontWeight="DemiBold" />
            <Label Content="00:00:00" Height="15" HorizontalAlignment="Right" Margin="0,18,0,0" Name="durationLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="38" FontWeight="DemiBold"/>

            <!--<Image Height="12" HorizontalAlignment="Left" Margin="0,3,0,0" Name="directionPictureBox" Stretch="Fill" VerticalAlignment="Top" Width="12"  />
            <Image Height="12" HorizontalAlignment="Right" Margin="0,20,41,0" Name="timerImage" Stretch="Fill" VerticalAlignment="Top" Width="12"          />
            <Image Height="12" HorizontalAlignment="Left" Margin="0,20,0,0" Name="dateTimeImage" Stretch="Fill" VerticalAlignment="Top" Width="12"       />-->


        </Grid>
    </Border>
    <Border BorderBrush="Gray" 
            BorderThickness="1,0,1,1" 
            Grid.Row="1" 
            Background="White"
            HorizontalAlignment="Stretch" 
            Margin="10,0,10,0" 
            Name="detailsPanel" 
            CornerRadius="0,0,3,3"
            VerticalAlignment="Stretch">
        <Grid HorizontalAlignment="Stretch" 
              Name="grid2" 
              Margin="3,0,3,0"
              VerticalAlignment="Stretch">
            <Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9" Padding="0" Margin="0,3,0,0" Name="numberRadLabel" VerticalAlignment="Top" />
            <Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9"  Padding="0" Margin="0,18,0,0" Name="queueRadLabel" VerticalAlignment="Top" />

        </Grid>
    </Border>
</Grid>

在用户控件中,我只有

         public UserControl1()
         {
            InitializeComponent();
         }

你是如何测量「内存使用情况」的?在Windows任务管理器中吗? - spender
1
@Alistad的意思是:你能提供一个最小化的工作示例代码来重新产生这个问题吗? - Heinzi
是的,那是开玩笑而不是冒犯。请复制一些代码,以便我们可以帮助。谢谢 @hHeinzi - Aliostad
@Aliostad:抱歉,我并不是想暗示你的请求有冒犯之处——恰恰相反。我只是想补充一下,他应该提供一个最小化的示例,而不是仅仅在这里倾泻他的完整源代码。;-) - Heinzi
@Henizi 不,我只是觉得我的语气可能有些过了。不管怎样,我现在认为没问题了。 - Aliostad
显示剩余3条评论
6个回答

11

我想立即释放内存。

不要这样做。相信垃圾回收器(GC)。

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

不要相信GC。

5-10分钟后,内存会被释放。

我没说过要相信GC吗?


  • 垃圾回收模型会确保在系统中释放不需要的托管内存(包括大部分控件的内存)。它使用一种算法来进行优化,其中包括代、可用的空闲内存、可能可用的CPU等,因此GC.Collect()会干扰它。

  • GC.Collect()是异步的,因此不会立即生效。

  • 唯一需要小心的资源是未经处理的非托管资源,通常由Dispose Pattern处理。否则不要干扰GC,它做得很好。


但我没有使用非托管资源(如果我没有弄错,非托管资源是本地资源处理程序、文件描述符等)。如果垃圾回收不能帮助我,我可以使用什么?在我们的应用程序中,这些控件将被添加和删除多次,如果这是内存泄漏,我会有问题,一段时间后应用程序将占用过多的内存并崩溃。 - Andriy Khrystyanovich
是的,这就是为什么你不需要担心它。我只是为了完整起见提到它。 - Aliostad
虽然在垃圾回收的环境中通常是正确的,但“不用担心”这种说法在这里有点含糊。如果你有一个窗口在泄漏(而且有很多可能的原因,比如没有取消订阅事件),andronz发布的方法是一种有效但有些巧妙的检测方式。我假设他发布的代码纯粹是为了测试目的。 - DavidN

4
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

这是一种强制非可垃圾回收对象过早进入Gen2的绝对方法,因此在更长的时间内增加了你的内存占用量,而没有任何好处。

正如Aliostad所说的:不要这样做!


3

不要管垃圾回收器,让它去完成自己的工作。

你所描述的并不是内存泄漏。只是动态内存没有在你认为应该释放的时刻被释放。

你是垃圾回收器吗?不是的。担心垃圾何时被收集不是你的任务。如果这些对象确实是垃圾 - 而它们就是 - 当你需要内存时,内存就会存在。


你是垃圾回收器吗?你不是... 我喜欢这个,我会使用它:D - Andy

1
在垃圾回收的环境中,立即释放内存并没有太多意义。
由于CLR按需JIT代码,第一次运行测试时,您不应该看到内存降回最初的水平。这是有道理的,因为已经跟随了新的代码路径并且代码已被JIT编译。那些代码需要驻留在内存中的某个地方,不是吗?
因此,在第一次测试运行后,您不应该能够将内存收集回到初始内存占用量。您的基线应该是运行测试一次后获得的内存使用情况,而不是之前。再运行一次后,我可以通过多次收集将内存恢复到基线。
另外,我建议以无调试器附加的发布模式运行项目。使用调试器附加运行程序将无法显示真实的内存配置文件,因为它采用了各种技巧来保留对象(例如Collect objects still in scope - GC.Collect)。
然而,这都是无关紧要的,因为如上所述,在大多数情况下,在GC环境中立即回收内存并没有太多意义。

0

通过使用此 Dll Invoke,我们可以重新分配内存资源。

public class MemoryManagement
{
[DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet =
CharSet.Ansi, SetLastError = true)]

private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int
maximumWorkingSetSize);

public static void FlushMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT) { SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}

0

我同意@Aliostad关于GC的观点。它非常出色地完成了它的工作,但它不是一个神奇地清除所有内存的工具。

如果您有内存泄漏问题,最直接和可靠的解决方案是使用分析器,它应该能够确定您是否有真正的泄漏以及它在哪里。我使用过Red Gate的Ants,但其他人可能有更好的建议。

除了遵循通常的准则,如确保您正确处理所有内容之外,调用GC并希望它能起作用并不是适当代码评估的替代品。


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