2010-11-20 16 views
13

मेरे पास एक ऐसा एप्लिकेशन है जिसमें ऑब्जेक्ट संदर्भ शून्य से सेट होने से पहले अलग होने वाली घटनाओं के कारण कुछ मेमोरी लीक हो। आवेदटन काफी बड़ा है और कोड को देखकर मेमोरी लीक ढूंढना मुश्किल है। मैं saks.dll का उपयोग उन तरीकों के नाम खोजने के लिए करना चाहता हूं जो लीक के स्रोत हैं लेकिन मैं अटक गया हूं। मैंने समस्या का प्रदर्शन करने के लिए एक परीक्षण परियोजना की स्थापना की।सी # इवेंट आधारित मेमोरी लीक

यहाँ मैं नीचे

namespace MemoryLeak 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      TestMemoryLeak testMemoryLeak = new TestMemoryLeak(); 

      while (!Console.ReadKey().Key.Equals('q')) 
      { 
      } 
     } 
    } 

    class TestMemoryLeak 
    { 
     public event EventHandler AnEvent; 

     internal TestMemoryLeak() 
     { 
      AnEventListener leak = new AnEventListener(); 
      this.AnEvent += (s, e) => leak.OnLeak(); 
      AnEvent(this, EventArgs.Empty); 
     } 

    } 

    class AnEventListener 
    { 
     public void OnLeak() 
     { 
      Console.WriteLine("Leak Event"); 
     } 
    } 
} 

मैं कोड को तोड़ने के रूप में 2 कक्षाएं, एक घटना के साथ, और उस घटना को सुनता है पर है, और मध्यवर्ती खिड़की प्रकार में

.load sos.dll 

तो मैं का उपयोग करें! dumpHeap प्रकार AnEventListener

!dumpheap -type MemoryLeak.AnEventListener 

के ढेर पर वस्तुओं को पाने के लिए और मैं के लिए मिलता है llowing

PDB symbol for mscorwks.dll not loaded 
Address  MT  Size 
01e19254 0040348c  12  
total 1 objects 
Statistics: 
     MT Count TotalSize Class Name 
0040348c  1   12 MemoryLeak.AnEventListener 
Total 1 objects 

मैं का उपयोग करें! बाहर काम करने के gcroot क्यों वस्तु जा रहा कचरा एकत्र नहीं है

!gcroot 01e19254 

और निम्नलिखित

!gcroot 01e19254 
Note: Roots found on stacks may be false positives. Run "!help gcroot" for 
more info. 
Error during command: Warning. Extension is using a callback which Visual Studio 
does not implement. 

Scan Thread 5208 OSTHread 1458 
ESP:2ef3cc:Root:01e19230(MemoryLeak.TestMemoryLeak)-> 
01e19260(System.EventHandler)-> 
01e19248(MemoryLeak.TestMemoryLeak+<>c__DisplayClass1)-> 
01e19254(MemoryLeak.AnEventListener) 
Scan Thread 7376 OSTHread 1cd0 

मैं अब ईवेंट हैंडलर है कि देख सकते हैं मिलता है रिसाव का स्रोत। मैं का उपयोग करें! कर ईवेंट हैंडलर के क्षेत्र को देखने के लिए और तो अब

!do 01e19260 
Name: System.EventHandler 
MethodTable: 65129dc0 
EEClass: 64ec39d0 
Size: 32(0x20) bytes 
    (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) 
Fields: 
     MT Field Offset     Type VT  Attr Value Name 
65130770 40000ff  4  System.Object 0 instance 01e19248 _target 
6512ffc8 4000100  8 ...ection.MethodBase 0 instance 00000000 _methodBase 
6513341c 4000101  c  System.IntPtr 1 instance 0040C060 _methodPtr 
6513341c 4000102  10  System.IntPtr 1 instance 00000000 _methodPtrAux 
65130770 400010c  14  System.Object 0 instance 00000000 _invocationList 
6513341c 400010d  18  System.IntPtr 1 instance 00000000 _invocationCount 

मिल रहा विधि है कि

0040C060 _methodPtr 

अलग नहीं किया जा रहा है, लेकिन मैं कैसे प्राप्त करने के लिए सूचक को देख सकते हैं उस विधि का नाम?

+1

कृपया इस प्रश्न का मेरा उत्तर देखें कि _methodPtr http://stackoverflow.com/questions/3668642/get-method-name-form-delegate-with-windbg पर क्या इंगित कर रहा है/3682594 # 3682594 –

+2

वाह, यह एक सुंदर था एक डीबगिंग परीक्षा का मोटा स्पष्टीकरण। एक विस्तृत उदाहरण पोस्ट करने के लिए समय निकालने के लिए उपरोक्त - और यह भविष्य में दूसरों के लिए उपयोग में आ सकता है। मैं इसे बुकमार्क करूंगा। –

उत्तर

1

अच्छे पुराने आईडीस्पोजेबल को लागू करने के बारे में क्या?

 class TestMemoryLeak : IDisposable 
     { 
       public event EventHandler AnEvent; 
       private bool disposed = false; 

      internal TestMemoryLeak() 
      { 
       AnEventListener leak = new AnEventListener(); 
       this.AnEvent += (s, e) => leak.OnLeak(); 
       AnEvent(this, EventArgs.Empty); 
      } 

      protected virtual void Dispose(bool disposing) 
      { 
       if (!disposed) 
       { 
       if (disposing) 
       { 
         this.AnEvent -= (s, e) => leak.OnLeak(); 
       } 
       this.disposed = true; 
       } 

      } 

      public void Dispose() 
      { 
       this.Dispose(true); 
       GC.SupressFinalize(this); 
      } 

    } 
+0

हाय मैक्स, उत्तर के लिए धन्यवाद। यह इस उदाहरण में अच्छा होगा, लेकिन असली एप्लिकेशन में, बहुत सारे कोड हैं और मुझे उस विधि का नाम पता नहीं है जिसे अलग करने की आवश्यकता है, इसलिए मैं इसे खोजने की कोशिश कर रहा हूं ... – Gaz

+0

@Gaz : लेकिन आप इस घटना से जुड़े प्रतिनिधियों की गणना करने के लिए इसका उपयोग कर सकते हैं, और यह पता लगाने के लिए कि कौन अलग नहीं हो रहा है, उन सभी प्रतिनिधियों की 'विधि' संपत्ति की जांच करें। –

+0

@ जिम मिशेल: क्या आप कृपया कुछ कोड जिम पोस्ट कर सकते हैं? – Gaz

1

मैं एक .NET स्मृति प्रोफाइलर का उपयोग कर इस मामले के दिल को पाने के लिए सिफारिश करेंगे, वहाँ कई हैं - व्यक्तिगत रूप से मैं पिछले जो एक 14 दिन की नि: शुल्क परीक्षण है में लाल गेट ANTS memory profiler का इस्तेमाल किया है :

http://www.red-gate.com/products/ants_memory_profiler/walkthrough.htm

4

घटनाक्रम, क्योंकि जब एक बी का सदस्य बनता मुश्किल कर रहे हैं दोनों एक दूसरे के लिए एक संदर्भ पकड़े जाते हैं। आपके उदाहरण में, यह कोई समस्या नहीं है क्योंकि कोई रिसाव नहीं है (एक निर्मित बी और बी का संदर्भ रखने वाला एकमात्र वस्तु है, इसलिए ए और बी मरने पर मर जाएगा)।

असली घटना की समस्याओं के लिए, यह "कमजोर घटनाओं" की अवधारणा को हल करेगा। दुर्भाग्यवश, सीएलआर से समर्थन के साथ 100% काम करने वाली कमजोर घटनाएं पाने का एकमात्र तरीका है। माइक्रोसॉफ्ट को यह समर्थन प्रदान करने में कोई दिलचस्पी नहीं है।

मैं आपको "सी # में कमजोर घटनाओं" की सलाह देता हूं और पढ़ना शुरू करता हूं। समस्या को हल करने के लिए आपको कई अलग-अलग दृष्टिकोण मिलेंगे, लेकिन आपको उनकी सीमाओं से अवगत होना चाहिए। कोई 100% समाधान नहीं है।

+0

हे टर्गीवर, जानकारी के लिए धन्यवाद। मैं भविष्य के संदर्भ के लिए कमजोर घटनाओं पर एक नज़र डालेगा। जिस आवेदन पर मैं काम कर रहा हूं, उसके साथ मुद्दा यह है कि आवेदन के जीवनकाल के लिए एक समन्वय वर्ग है। और यह इस वर्ग की घटनाएं हैं जो स्मृति रिसाव पैदा कर रही हैं, क्योंकि इस समन्वयक वर्ग की घटनाओं के संदर्भ में किसी भी ग्राहक को जीवित रखा जा रहा है। – Gaz

+0

कमजोर घटना पैटर्न के बिना स्थिर घटनाओं से निपटने पर, आपके पास एकमात्र विकल्प है जो आपके अंतिम संदर्भ गायब होने से पहले सदस्यता समाप्त करना है (जिसके बाद आपके पास सदस्यता समाप्त करने का कोई साधन नहीं है)। – Tergiver

+0

मुझे कहना चाहिए, "अंतिम * दृश्य * संदर्भ"। – Tergiver

1

IDisposable विचार है कि @Max Malygin प्रस्तावित पर विस्तार:

नीचे कोड एक घटना पर बकाया संचालकों के लिए जाँच करने के लिए कैसे पता चलता है।

कक्षा में Tick घटना है जो प्रति सेकंड एक बार आग लगती है। जब Dispose कहा जाता है, तो कोड इनवॉलर्स सूची में हैंडलर को बताता है (यदि कोई है), और उस वर्ग और विधि नाम को आउटपुट करता है जो अभी भी ईवेंट की सदस्यता लेता है।

कार्यक्रम किसी ऑब्जेक्ट को तुरंत चालू करता है, एक ईवेंट हैंडलर को जोड़ता है जो घटना को निकाल दिए जाने पर हर बार "टिक" लिखता है, और फिर 5 सेकंड तक सो जाता है। फिर यह ईवेंट हैंडलर को अन-सब्सक्राइब किए बिना ऑब्जेक्ट का निपटान करता है।

using System; 
using System.Diagnostics; 
using System.Threading; 

namespace testo 
{ 
    public class MyEventThing : IDisposable 
    { 
     public event EventHandler Tick; 
     private Timer t; 

     public MyEventThing() 
     { 
      t = new Timer((s) => { OnTick(new EventArgs()); }, null, 1000, 1000); 
     } 

     protected void OnTick(EventArgs e) 
     { 
      if (Tick != null) 
      { 
       Tick(this, e); 
      } 
     } 

     ~MyEventThing() 
     { 
      Dispose(false); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     private bool disposed = false; 
     private void Dispose(bool disposing) 
     { 
      if (!disposed) 
      { 
       if (disposing) 
       { 
        t.Dispose(); 
        // Check to see if there are any outstanding event handlers 
        CheckHandlers(); 
       } 

       disposed = true; 
      } 
     } 

     private void CheckHandlers() 
     { 
      if (Tick != null) 
      { 
       Console.WriteLine("Handlers still subscribed:"); 
       foreach (var handler in Tick.GetInvocationList()) 
       { 
        Console.WriteLine("{0}.{1}", handler.Method.DeclaringType, handler.Method.Name); 
       } 
      } 
     } 

    } 

    class Program 
    { 
     static public long Time(Action proc) 
     { 
      Stopwatch sw = Stopwatch.StartNew(); 
      proc(); 
      return sw.ElapsedMilliseconds; 
     } 

     static int Main(string [] args) 
     { 
      DoIt(); 
      Console.WriteLine(); 
      Console.Write("Press Enter:"); 
      Console.ReadLine(); 
      return 0; 
     } 

     static void DoIt() 
     { 
      MyEventThing thing = new MyEventThing(); 
      thing.Tick += new EventHandler(thing_Tick); 
      Thread.Sleep(5000); 
      thing.Dispose(); 
     } 

     static void thing_Tick(object sender, EventArgs e) 
     { 
      Console.WriteLine("tick"); 
     } 
    } 
} 

उत्पादन होता है:

Handlers still subscribed: 
testo.Program.thing_Tick 
0

आप WinDbg

पर इस तरह की कोशिश कर सकते
  1. डंप लक्ष्य Obj विधि टेबल पाने के लिए: dumpobj 01e19248
  2. डंप विधि टेबल इसमें 0040C060 खोजने के लिए: ! dumpmt -md 0ced1910
  3. अगर कोई मैच, स्मृति कि _methodPtr पते से शुरू डंप: यू 0040C060
  4. खोजें जेएमपी या चाल अनुदेश और उनके पते, जैसे डंप: यू 0cf54930

जाएँ यहां अधिक जानकारी के लिए: http://radheyv.blogspot.com/2011/04/detecting-memory-leaks-in-silverlight.html

संबंधित मुद्दे