2012-01-24 5 views
7

대용량의 메모리를 할당하는 컨트롤이 UI의 입력 이벤트를 사용하여 파괴되고 재구성되는 경우 문제가 발생했습니다.무거운 WPF 컨트롤에서 결정적으로 사용되는 메모리를 해제하는 방법은 무엇입니까?

분명히, Window의 Content를 null로 설정하는 것은 포함 된 Control이 사용하고있는 메모리를 해제하기에 충분하지 않습니다. 결국 GCed 될 것입니다. 그러나 컨트롤을 파괴하고 구성하는 입력 이벤트가 더 자주 발생함에 따라 GC는 일부 개체를 건너 뛴 것으로 보입니다.

나는이 예에 고장 :

XAML :

<Window x:Class="WpfApplication1.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" Height="350" Width="525" x:Name="window" MouseMove="window_MouseMove"> 
</Window> 

C 번호 :

using System; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Input; 

namespace WpfApplication1 
{ 
    public partial class MainWindow : Window 
    { 
     private class HeavyObject : UserControl 
     { 
      private byte[] x = new byte[750000000]; 

      public HeavyObject() 
      { 
       for (int i = 0; i++ < 99999999;) { } // just so we can follow visually in Process Explorer 
       Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove 
      } 
     } 

     public MainWindow() 
     { 
      InitializeComponent(); 

      //Works 1: 
      //while (true) 
      //{ 
      // window.Content = null; 
      // window.Content = new HeavyObject(); 
      //} 
     } 

     private void window_MouseMove(object sender, MouseEventArgs e) 
     { 
      if (window.Content == null) 
      { 
       GC.Collect(); 
       GC.WaitForPendingFinalizers(); 
       // Works 2: 
       //new HeavyObject(); 
       //window.Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove 
       //return; 
       window.Content = new HeavyObject(); 
      } 
      else 
      { 
       window.Content = null; 
      } 
     } 
    } 
} 

이 각 MouseMove 이벤트에서 ~의 750메가바이트 개체 (HeavyObject)을 할당 넣어 않습니다 그것은 메인 윈도우의 콘텐츠입니다. 커서가 창에서 계속 유지되면 내용 변경으로 끝없이 이벤트가 다시 트리거됩니다. 헤비 오브젝트에 대한 참조가 다음 구성 전에 무시되고 GC에 대한 쓸데없는 시도가 트리거됩니다.

ProcessExplorer/Taskman에서 볼 수있는 것은 가끔 1.5GB 이상이 할당된다는 것입니다. 그렇지 않다면, 마우스를 격렬히 움직여서 창을 닫고 다시 입력하십시오. LARGEADDRESSAWARE없이 x86 용으로 컴파일하면 대신 OutOfMemoryException이 발생합니다 (~ 1.3GB 제한).

HeavyObject를 마우스 입력 ("Works 1"섹션의 주석 처리 제거)을 사용하지 않고 무한 루프에 할당하거나 마우스 입력으로 할당을 트리거하지만 개체를 시각적 트리 (주석 2의 "주석 2").

그래서 시각적 트리를 지연시키는 리소스와 관련이 있다고 가정합니다. 그러나 또 다른 이상한 효과가 있습니다. 커서가 Window 외부에있을 때 1.5GB가 소비되면 MouseMove가 다시 트리거 될 때까지 GC가 시작되지 않는 것 같습니다. 적어도 메모리 소비는 더 이상의 이벤트가 발생하지 않는 한 안정화되는 것으로 보입니다. 그래서 활동이 없을 때 어딘가에 오래 살아있는 참조가 남아 있거나 GC가 게으르다.

나에게 이상하게 보입니다. 무슨 일이 일어나는지 알아낼 수 있니?

편집 : BalamBalam이 주석으로 달음 : 컨트롤을 파괴하기 전에 컨트롤 뒤에있는 데이터를 역 참조 할 수 있습니다. 그것은 계획 B 였을 것입니다. 그럼에도 불구하고 좀 더 일반적인 해결책이있을 수 있습니다.

그런 컨트롤을 말하는 것은 나쁜 코드는 도움이되지 않습니다. 내가 왜 dereferencing 후 몇 가지 틱에 대한 UI를 혼자두고 떠날 경우 GCed 얻을 개체 참조를 보유하고 싶습니다,하지만 영원히 주위에 (사용자 입력에 의해 만들어 지도록했습니다) 사용자 입력 즉시 걸립니다. 장소.

+2

나는 당신의 문제가 무엇인지 알 수 없습니다. 여기에있는 미친 코드가 테스트 용일 뿐이라고 가정합니다. 그러나 당신은 한 가지를 배웠습니다. GC를 강요하려는 시도는 종종 자신이 생각하는대로하지 않습니다. –

+0

코드는 문제가 발생하지 않는 두 가지 해결 방법을 포함하는 예제입니다. 단지 가정을 제한하기 위해서. 문제는 다음과 같습니다 : 창 콘텐트로 큰 콘트롤 -> 창 콘텐트로 새 콘트롤 설정 ** ** 마우스 입력으로 ** -> 한 번에 두 개 이상의 빅 컨트롤이 살아 있습니다. –

+0

글쎄, 아무도 마우스 이동시 750MB의 오브젝트를 할당하지 않기를 바랍니다. 그것은 매우 비현실적인 시나리오입니다. 그래서 여러분의 전체 재단은 흔들립니다. –

답변

7

좋아, 여기서 무슨 일이 일어날 지 알 것 같아. 소금 한 방울로 가져 가라.

코드를 약간 수정했다.

using System; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Input; 

namespace WpfApplication1 
{ 
    public partial class MainWindow : Window 
    { 
     private class HeavyObject : UserControl 
     { 
      private byte[] x = new byte[750000000]; 

      public HeavyObject() 
      { 
       for (int i = 0; i < 750000000; i++) 
       {      
        unchecked 
        { 
         x[i] = (byte)i;  
        } 
       } 

       // Release optimisation will not compile the array 
       // unless you initialize and use it. 
       Content = "Loadss a memory!! " + x[1] + x[1000]; 
      } 
     } 

     const string msg = " ... nulled the HeavyObject ..."; 

     public MainWindow() 
     { 
      InitializeComponent(); 
      window.Content = msg; 
     } 

     private void window_MouseDown(object sender, MouseEventArgs e) 
     { 
      if (window.Content.Equals(msg)) 
      { 
       window.Content = new HeavyObject(); 
      } 
      else 
      { 
       window.Content = msg; 
      } 
     } 
    } 
} 

뒤에

XAML

<Window x:Class="WpfApplication1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="350" Width="525" x:Name="window" MouseDown="window_MouseDown"> 
</Window> 

코드는 이제 이것을 실행하고 창을 클릭합니다. 코드를 수정하지 GC.Collect()메모리를 mousedown에 설정. 릴리스 모드을 붙이지 않고 을 실행하면 컴파일됩니다.

창을 천천히 클릭하십시오.

메모리 사용량이 처음 나타나는 것은 750MBytes입니다. 후속 클릭은 천천히 클릭하는 한 .. Nulled the heavy object..Loads a memory!사이에서 메시지를 전환합니다. 메모리가 할당 및 할당 해제 될 때 약간의 지연이 있습니다. 메모리 사용량은 750MBytes를 넘지 않아야하지만 heavyobject가 null 인 경우에도 메모리 사용량이 감소하지 않습니다. 이것은 디자인으로 GC Large Object Heap is lazy and collects large object memory when new large object memory is needed입니다. MSDN에서 :

큰 개체 할당 요청을 수용 할 여유 공간이 충분하지 않으면 먼저 OS에서 더 많은 세그먼트를 얻으려고 시도합니다. 만약 실패한다면, 나는 약간의 공간을 확보하기 위해 2 세대 가비지 콜렉션을 트리거 할 것이다.

클릭 빠른 창에

20 클릭 정도. 무슨 일이야? 내 PC에서 메모리 사용량이 올라가지 않지만 메인 윈도우는 이제 메시지를 더 이상 업데이트하지 않습니다. 얼어 버린다. 메모리 부족 예외가 발생하지 않으며 시스템의 전체 메모리 사용량이 일정 수준으로 유지됩니다.

스트레스 창에 MouseMove 명령어를 사용하여 시스템

지금 (귀하의 질문에 코드 당)이 XAML 변경하여 MouseMove 이벤트에 대한 MouseDown을 대체 : 나는 마우스를 이동할 수 있습니다

<Window x:Class="WpfApplication1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="350" Width="525" x:Name="window" MouseMove="window_MouseDown"> 
</Window> 

을 그것을 잠시 동안 지나치지만 결국 OutofMemoryException을 던진다.. 예외를 강제하는 좋은 방법은 여러 번 이동 한 다음 창을 최대화하는 것입니다.

이론

좋아, 그래서 이것은 내 이론이다. UI 스레드가 메시지 루프 이벤트 (MouseMove) 내에서 메모리를 만들고 삭제하는 일이 없기 때문에 가비지 수집기는 계속 유지할 수 없습니다. 대형 오브젝트 힙의 가비지 콜렉션은 when memory is needed입니다. 가능한 한 드물게 수행되는 값 비싼 작업이기도합니다.

MouseMove에서 수행 할 때 빠르게 켜기/끄기/켜기/끄기를 클릭 할 때 느린 지연이 발생합니다. MSDN 기사에 따르면 다른 큰 개체가 할당 될 경우 GC는 수집을 시도하거나 (메모리를 사용할 수없는 경우) 디스크를 사용하기 시작합니다.

시스템의 메모리가 부족합니다. 상황 : 이것은 OS에서 높은 메모리 알림을 수신하는 경우 발생합니다. 2 세대 GC를 수행하면 생산성이 향상된다고 생각하면 하나를 트리거합니다.

이제이 부분이 재미 있고 내가 여기 추측하고있어하지만 마지막

, 지금의 한, LOH는 수집의 일환으로 압축되지 않은,하지만해야 구현 세부입니다 의지하지 말라. 따라서 GC로 무언가가 움직이지 않도록 항상 고정하십시오. 이제 새로 발견 된 LOH 지식을 가지고 가서 힙을 제어하십시오. 대형 개체 힙 압축하지 않고, 당신이 꽉 루프에서 여러 큰 개체를 요청하는 경우

그래서, 그것은 컬렉션 결국 이론적으로 충분한 메모리가 있어야에도 불구하고에서 OutOfMemoryException을 일으키는 분열로 이어질 가능성이있다?

솔루션

하자까지 무료로 UI는 GC 룸 숨을 쉴 수 있도록 조금 스레드. 할당 코드를 비동기 Dispatcher 호출로 감싸고 SystemIdle과 같은 낮은 우선 순위를 사용합니다. 이 방법은 에만 UI 스레드가 비어있을 때 메모리를 할당합니다.

private void window_MouseDown(object sender, MouseEventArgs e) 
    { 
     Dispatcher.BeginInvoke((ThreadStart)delegate() 
      { 
       if (window.Content.Equals(msg)) 
       { 
        window.Content = new HeavyObject(); 
       } 
       else 
       { 
        window.Content = msg; 
       } 
      }, DispatcherPriority.ApplicationIdle); 
    } 

나는 모든 형태 위에 마우스 포인터를 춤을 수있는이 사용하고 그것은 결코에서 OutOfMemoryException가 발생하지 않습니다. 그것이 정말로 작동하든, 또는 테스트할만한 가치가 있는지는 모르겠다. 결론적으로

이 크기의 물체가 충분하지 않은 공간이

  • LOH가 가능한 경우 힙
  • LOH 할당은 GC를 트리거 대형 오브젝트에 할당 될 예정

    • 주 GC주기 중에 압축되지 않아 이론적으로 충분한 메모리가 있더라도 OutOfMemoryException이 발생하여 조각화가 발생할 수 있습니다.
    • 큰 개체를 이상적으로 재사용하고 다시 할당하지 마십시오. 이 방법을 사용할 수없는 경우
      • Allcate 백그라운드 스레드에 큰 개체는
      • 는 GC 룸 숨을 쉴 수 있도록하려면 UI 스레드에서 분리 응용 프로그램이 유휴 상태가 될 때까지 할당을 분리하는 Dispatcher를 사용하는
  • +0

    이 고마워요. 나는 당신의 시험을 재현했습니다. 내 컴퓨터에서 "20 Clicks"= Freeze가 발생하지 않았습니다. 창은 모든 클릭이 처리 될 때까지 변경되지 않고 그대로있었습니다 (잠시 시간이 걸렸음). 아마 오래 기다리지 않았을거야? 메모리 동작은 여기에서 동일합니다. 스트레스를 받고 창을 최대화하는 것은 마우스 커서로 창을 나가고 다시 입력하는 것과 완전히 똑같은 것처럼 보입니다 (원래 질문 에서처럼). 어쩌면 떠나고 이벤트가 메모리를 할당하여 다음 HeavyObject가 더 이상 이전 간격의 갭에 맞지 않게 할 수 있습니까? –

    +1

    @die_hoernse를 사용하면 소프트웨어의 속도를 낮추기 위해 우선 순위가 낮은 Dispatcher를 사용하여 DoBigMemoryOperation 함수에서 Click 이벤트를 분리하고 BigMemoryOperation이 반환 될 때까지 재진입을 막을 수 있습니다. 백그라운드 스레드에서 BigMemoryOperation을 수행하면 진행 상황을보고하고 UI/비활성화 버튼을 회색으로 표시하는 등의 작업을 수행하면 GC에 스트레스를 가할 때 실제로 프로덕션 충돌을 방지 할 수 있습니다. –

    +0

    "스트레스"모드에서 두 번 more - HeavyObject의 메모리가 750MB 대신 250 개가 됨) 커서가 창 외부에 있으면 커서가 다시 입력 될 때까지 할당 된 상태를 유지합니다. 내 이론은 GC가 프로그램의 비활성 및 시스템 메모리 부족으로 인해 유휴 상태라는 것입니다. 누출은 없지만 LOH가 결국 압축 될 것이라는 점을 감안할 때 최종 결론은 다음과 같은 솔루션을 고려해야한다는 것을 의미합니다.이를 백그라운드 스레드에 보관하십시오. 그러나 추측 할 수있는 I/O 또는 CPU로드가 없어도 메모리가 영구적으로 할당 된 채로 남아있는 경우이 질문이 업데이트됩니다. –

    관련 문제