الأصل: http://www.micel.cn/article.asp?id=1698
هل تعرف آلية جمع القمامة في .Net؟ هل يمكنك أن تصف بإيجاز كيفية عمل GC؟ كيف يمكننا إدارة الذاكرة بشكل فعال؟ ما هو دور الكائن الذي تم إنشاء مثيل له في نص عبارة الاستخدام؟
تم تنظيم هذا القسم على النحو التالي، 1. أنواع الشبكة وتخصيص الذاكرة 2. كيفية عمل أداة تجميع البيانات المهملة في GC 3. ما هي الموارد غير المُدارة 4. كيفية تحرير موارد الكائن بشكل فعال. ملخص لنبدأ دراستنا لهذا القسم الآن.
1..أنواع الشبكة وتخصيص الذاكرة
كافة الأنواع في Net مشتقة (بشكل مباشر أو غير مباشر) من نوع System.Object.
تنقسم الأنواع في CTS إلى فئتين - الأنواع المرجعية (الأنواع المرجعية، وتسمى أيضًا الأنواع المُدارة)، والتي يتم تخصيصها في كومة الذاكرة، وأنواع القيمة. يتم تخصيص أنواع القيمة على المكدس. كما هو موضح في الصورة
أنواع القيمة موجودة في المكدس، أول ما يدخل وآخر يخرج، يكون لمتغيرات نوع القيمة ترتيب حياة، مما يضمن أن متغيرات نوع القيمة ستحرر الموارد قبل أن يتم دفعها خارج النطاق. أبسط وأكثر كفاءة من الأنواع المرجعية. يقوم المكدس بتخصيص الذاكرة من العناوين العالية إلى العناوين المنخفضة.
يتم تخصيص نوع المرجع على الكومة المُدارة (الكومة المُدارة)، ويتم الإعلان عن متغير وحفظه في المكدس. عند استخدام جديد لإنشاء كائن، يتم تخزين عنوان الكائن في هذا المتغير. على العكس من ذلك، تقوم الكومة المُدارة بتخصيص الذاكرة من العناوين المنخفضة إلى العناوين العالية، كما هو موضح في الشكل
2. كيف يعمل جامع البيانات المهملة GC
في الشكل أعلاه، عند انتهاء صلاحية مجموعة البيانات، لا نعرض تدمير الكائن، وتستمر الكائنات الموجودة في الكومة في الوجود، في انتظار إعادة تدوير GC.
من المستحسن ولكن ليس مطلوبًا أن يدعم جامع البيانات المهملة تقادم الكائن من خلال الإنشاء. الجيل هو وحدة من الكائنات ذات الأعمار النسبية في الذاكرة. هدف
يحدد الاسم الرمزي أو العمر الجيل الذي ينتمي إليه الكائن. في دورة حياة التطبيق، تنتمي الكائنات التي تم إنشاؤها مؤخرًا إلى جيل أحدث ولها قيم أعلى من الكائنات التي تم إنشاؤها سابقًا.
رقم الرمز الفرعي السفلي. رمز الكائن في أحدث جيل هو 0.
عند إنشاء كائن جديد، يجب عليك أولاً البحث في القائمة المرتبطة المجانية للعثور على كتلة الذاكرة الأكثر ملاءمة، وتخصيصها، وضبط القائمة المرتبطة بكتلة الذاكرة، ودمج الأجزاء. يمكن إكمال العملية الجديدة في وقت O(1) تقريبًا، وذلك بإضافة 1 إلى المؤشر العلوي للكومة. مبدأ العمل هو: عندما تكون المساحة المتبقية على الكومة المُدارة غير كافية، أو عندما تكون مساحة المولد 0 ممتلئة، يتم تشغيل GC ويبدأ في استعادة الذاكرة. في بداية جمع البيانات المهملة، يقوم GC بضغط ذاكرة الكومة وضبطها، ويتم تركيز الكائنات في الأعلى. سيشغل GC قدرًا معينًا من وقت وحدة المعالجة المركزية عند فحص البيانات المهملة. تقوم خوارزمية GC الأصلية بفحص الكومة بأكملها، وهو أمر غير فعال. يقسم GC الحالي الكائنات الموجودة في الكومة إلى ثلاثة أجيال. أحدث إدخال في الكومة هو الجيل 0، متبوعًا بالجيل 1 والجيل 2. يقوم GC الأول بمسح الجيل 0 فقط. إذا كانت المساحة المستصلحة كافية للاستخدام الحالي، ليست هناك حاجة لمسح الكائنات في الأجيال الأخرى. لذلك، يقوم GC بإنشاء كائنات بشكل أكثر كفاءة من C++ ولا يحتاج إلى فحص مساحة الكومة بأكملها. يعد تحسين الأداء الناتج عن إستراتيجية المسح واستراتيجية إدارة الذاكرة كافيًا للتعويض عن وقت وحدة المعالجة المركزية الذي يشغله GC.
3. ما هي الموارد غير المُدارة؟
المورد الشائع غير المُدار هو كائن يغلف مورد نظام التشغيل، مثل ملف أو نافذة أو اتصال بالشبكة. بالنسبة لمثل هذه الموارد، على الرغم من أن أداة تجميع البيانات المهملة يمكنها تتبع عمر الكائن الذي يغلف المورد غير المُدار، إلا أنها تعرف كيفية القيام بذلك. تنظيف هذه الموارد. لحسن الحظ، تسمح طريقة Finalize() التي يوفرها .net Framework بتنظيف الموارد غير المُدارة بشكل صحيح قبل أن يقوم جامع البيانات المهملة بإعادة تدوير هذه الموارد. فيما يلي العديد من الموارد الشائعة غير المُدارة: الفرش، وكائنات الدفق، والكائنات المكونة والموارد الأخرى (Object، وOdbcDataReader، وOleDBDataReader، وPen، وRegex، وSocket، وStreamWriter، وApplicationContext، وBrush،
مكون، ComponentDesigner، حاوية، سياق، المؤشر، FileStream،
الخط، الأيقونة، الصورة، المصفوفة، الموقت، تلميح الأدوات). (راجع MSDN)
4. كيفية تحرير الموارد غير المُدارة بشكل فعال.
لا يستطيع GC إدارة الموارد غير المُدارة، فكيف يتم تحرير الموارد غير المُدارة؟ يوفر .Net طريقتين:
(1) المدمر: عندما يقوم جامع البيانات المهملة بإعادة تدوير موارد كائن غير مُدار، فسوف يستدعي طريقة إنهاء الكائن () لتنظيف الموارد، ومع ذلك، نظرًا لقيود قواعد عمل GC، يستدعي GC إنهاء الكائن لن يتم تحرير المورد مرة واحدة، وسيتم حذف الكائن بعد الاستدعاء الثاني.
(2) ترث واجهة IDisposable وتنفذ طريقة Dispose(). تحدد واجهة IDisposable نمطًا (مع دعم على مستوى اللغة)، وتوفر آلية معينة لتحرير الموارد غير المُدارة، وتتجنب المشكلات الكامنة في مجموعة البيانات المهملة للأجهزة المدمرة - القضايا ذات الصلة.
من أجل فهم آلية جمع البيانات المهملة بشكل أفضل، كتبت جزءًا من الكود خصيصًا وأضفت تعليقات مفصلة. حدد فئة واحدة FrankClassWithDispose (ترث الواجهة IDisposable)، وFrankClassNoFinalize (بدون أداة نهائية)، وFrankClassWithDestructor (يحدد المدمر).
الكود المحدد هو كما يلي:
شفرة
1 باستخدام النظام؛
2 باستخدام System.Collections.Generic؛
3 باستخدام System.Text؛
4 باستخدام System.Data؛
5 باستخدام System.Data.Odbc؛
6 باستخدام System.Drawing؛
7 // مشفرة بواسطة فرانك شو لي 18/2/2009
8 // دراسة إدارة ذاكرة .NET
9 // جامع القمامة جامع القمامة. يمكن استعادة الموارد المستضافة عند الحاجة وفقًا للسياسات،
10 // لكن GC لا يعرف كيفية إدارة الموارد غير المُدارة. مثل اتصال الشبكة، واتصال قاعدة البيانات، والفرشاة، والمكون، وما إلى ذلك.
11 // آليتان لحل مشكلة تحرير الموارد غير المدارة. المدمرة، واجهة IDispose
12 // عدد مرجع COM
13 // الإدارة اليدوية لـ C++، حذف جديد
14 // الإدارة التلقائية لـ VB
15 إدارة مساحة الاسم
16 {
17 // وراثة الواجهة IDisposable، وتنفيذ طريقة التخلص، وتحرير موارد مثيل FrankClassDispose
18 فئة عامة FrankClassWithDispose: IDisposable
19 {
20 خاص OdbcConnection _odbcConnection = null;
واحد وعشرون
22 // منشئ
23 FrankClassWithDispose() العامة
أربعة وعشرون {
25 إذا (_odbcConnection == null )
26 _odbcConnection = new OdbcConnection();
27 Console.WriteLine("تم إنشاء FrankClassWithDispose");
28 }
29 //طريقة الاختبار
30 الفراغ العام DoSomething ()
31 {
32
33 /**/ /// /كود هنا لفعل شيء ما
34 عودة؛
35}
36 // تنفيذ التخلص من الموارد التي تستخدمها هذه الفئة وتحريرها
37 التخلص من الفراغ العام ()
38 {
39 إذا (_odbcConnection != null )
40 _odbcConnection.Dispose();
41 Console.WriteLine("تم التخلص من FrankClassWithDispose");
42 }
43}
44 // لم يتم تنفيذ الإنهاء، انتظر حتى يقوم GC بإعادة تدوير موارد مثيل FrankClassFinalize، وإعادة تدويرها مباشرة عند تشغيل GC.
45 فئة عامة FrankClassNoFinalize
46 {
47 خاص OdbcConnection _odbcConnection = null ;
48 //منشئ
49 FrankClassNoFinalize() العامة
50 {
51 إذا (_odbcConnection == فارغ)
52 _odbcConnection = new OdbcConnection();
53 Console.WriteLine("تم إنشاء FrankClassNoFinalize");
54 }
55 //طريقة الاختبار
56 الفراغ العام DoSomething ()
57 {
58
59 // جي سي. كوليكت ()؛
60 /**/ /// /كود هنا لفعل شيء ما
61 عودة؛
62 }
63}
64 // تنفيذ المدمر وتجميعه في طريقة الإنهاء واستدعاء مدمر الكائن
65 // عند تشغيل GC، لا يتم تحرير المورد في المكالمة الأولى ولكن فقط في المكالمة الثانية.
66 //موارد مثيل FrankClassDestructor
67 // يستخدم CLR مؤشر ترابط مستقل لتنفيذ طريقة الإنهاء للكائن. ستؤدي المكالمات المتكررة إلى انخفاض الأداء.
68 فئة عامة FrankClassWithDestructor
69 {
70 خاص OdbcConnection _odbcConnection = null ;
71 //منشئ
72 FrankClassWithDestructor() العامة
73 {
74 إذا (_odbcConnection == فارغ)
75 _odbcConnection = new OdbcConnection();
76 Console.WriteLine("تم إنشاء FrankClassWithDestructor");
77 }
78 //طريقة الاختبار
79 الفراغ العام DoSomething ()
80 {
81 /**/ /// /الرمز هنا لفعل شيء ما
82
83 عودة؛
84}
85 // المدمر، يطلق الموارد غير المدارة
86 ~ فرانك كلاس ويذ ديستركتور()
87 {
88 إذا (_odbcConnection! = فارغ)
89 _odbcConnection.Dispose();
90 Console.WriteLine("تم التخلص من FrankClassWithDestructor");
91 }
92 }
93}
94
يتم استخدام مثيل للكائن غير المُدار OdbcConnection. تم اختبار العميل المدمج لفترة وجيزة. رمز العميل هو كما يلي:
شفرة
1 باستخدام النظام؛
2 باستخدام System.Collections.Generic؛
3 باستخدام System.Text؛
4 باستخدام System.Data؛
5 باستخدام إدارة الذاكرة؛
6 // مشفرة بواسطة فرانك شو لي 18/2/2009
7 // دراسة إدارة ذاكرة .NET
8 // اختبار الكائنات غير المُدارة المستصلحة.
9 // اختبارات التعليمات البرمجية غير المُدارة والمقارنة
10 // بالنسبة للتعليمات البرمجية المُدارة، يمكن لـ GC إعادة تدويرها بنفسها بشكل أكثر إستراتيجية، أو يمكنها تنفيذ IDisposable، واستدعاء طريقة Dispose()، وإصدارها بشكل نشط.
11 مساحة الاسم MemoryManagementClient
12 {
13 برنامجًا دراسيًا
14 {
15 فراغًا ثابتًا رئيسيًا (سلسلة [] وسيطات)
16 {
17
18 /**/ ///////////////////////////////// //(1 ) / /////////////////////////////////////////////////////////////
19 //استدعاء طريقة التخلص () للإصدار النشط. الموارد، المرونة
20 FrankClassWithDispose _frankClassWithDispose = null ;
21 محاولة
إثنان وعشرون {
23 _frankClassWithDispose = new FrankClassWithDispose();
24 _frankClassWithDispose.DoSomething();
25
26}
27 أخيرا
28 {
29 إذا (_frankClassWithDispose != null )
30 _frankClassWithDispose.Dispose();
31 // Console.WriteLine("تم إصدار مثيل FrankClassWithDispose");
32}
33
34 /**/ ///////////////////////////////// //(2 ) / ////////////////////////////////////////////////////////
35 // يمكنك استخدام عبارة الاستخدام لإنشاء كائن غير مُدار، وسيتم استدعاؤه قبل انتهاء تنفيذ الطريقة
36 استخدام (FrankClassWithDispose _frankClassWithDispose2 = جديد FrankClassWithDispose())
37 {
38 // _frankClassWithDispose2.DoSomething();
39 }
40
41 /**/ ///////////////////////////////// //(3 ) / /////////////////////////////////////////////////////////////
42 //عند تشغيل أداة تجميع البيانات المهملة، يتم تحرير الموارد مرة واحدة
43 FrankClassNoFinalize _frankClassNoFinalize = new FrankClassNoFinalize();
44 _frankClassNoFinalize.DoSomething();
45
46 /**/ ///////////////////////////////////////////////////// (4) /////////////////////////////////////////////////////// / /
47 // عند تشغيل أداة تجميع البيانات المهملة، يستغرق الأمر مرتين لتحرير الموارد.
48 FrankClassWithDestructor _frankClassWithDestructor = new FrankClassWithDestructor();
49 _frankClassWithDestructor.DoSomething();
50 /**/ ////////////////////////////////////////////////////// (5) /////////////////////////////////////////////////////// /
51 // لا يمكن استخدام عبارة الاستخدام لإنشاء كائن لأنها لا تنفذ واجهة IDispose.
52 // استخدام (FrankClassWithDestructor _frankClassWithDestructor2 = new FrankClassWithDestructor())
53 // {
54 // _frankClassWithDestructor2.DoSomething();
55 // }
56
57 /**/ ///////////////////////////////////////////////////// /// ////////////////////////////////////////////////////////////
58 // للتصحيح
59 Console.WriteLine("اضغط على أي مفتاح للمتابعة");
60 Console.ReadLine();
61
62
63}
64}
65 }
66
في بعض الأحيان، يجب تحرير الموارد في وقت محدد. يمكن للفئة تنفيذ الواجهة IDisposable التي تنفذ إدارة الموارد وأساليب مهمة التنظيف IDisposable.Dispose.
إذا كان المتصل بحاجة إلى استدعاء أسلوب التخلص لتنظيف الكائن، فيجب على الفصل تنفيذ أسلوب التخلص كجزء من العقد. لا يتم استدعاء جامع البيانات المهملة بشكل افتراضي
طريقة التخلص؛ ومع ذلك، يمكن لتطبيق طريقة التخلص استدعاء الأساليب في GC لتنظيم السلوك النهائي لمجمع البيانات المهملة.
ومن الجدير بالذكر أن: استدعاء طريقة Dispose() يؤدي إلى تحرير الموارد بشكل فعال وهو مرن. يمكنك استخدام عبارة الاستخدام لإنشاء كائنات غير مُدارة، وسيتم استدعاؤها
تقوم طريقة Dispose () بتحرير الموارد بنفس تأثير طرفي الكود. يمكنك عرض IL المترجم.
شفرة
1. حاول
2 {
3 إيل_0003: لا
4 IL_0004: مثيل newobj باطل [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
5 إيل_0009: ستلوك 0
6 إيل_000أ: لدولوك 0
7 IL_000b: مثيل callvirt باطل [MemoryManagement]MemoryManagement.FrankClassWithDispose::DoSomething()
8 إيل_0010: لا
9 إيل_0011: لا
10 IL_0012: اترك IL_0028
11 } // النهاية .محاولة
12 أخيرا
13 {
14 إيل_0014: لا
15 إيل_0015: لدوك 0
16 IL_0016: ldnull
17 IL_0017: مكافئ
18 IL_0019: stloc.s CS$ 4 0000 دولار
19 IL_001b: ldloc.s CS$ 4 0000 دولار
20 IL_001d: brtrue.s IL_0026
21 إيل_001f: لدولوك 0
22 IL_0020: مثيل callvirt باطل [MemoryManagement]MemoryManagement.FrankClassWithDispose::Dispose()
23 إيل_0025: لا
24 إيل_0026: لا
25 IL_0027: أخيرا
26 } // معالج النهاية
27 إيل_0028: لا
28 IL_0029: مثيل newobj باطل [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
29 إيل_002e: ستلوك 1
30. حاول
31 {
32 IL_002f: لا
33 إيل_0030: لا
34 IL_0031: إجازة IL_0045
35 } // النهاية .محاولة
36 أخيرا
37 {
38 إيل_0033: لدولوك 1
39 IL_0034: ldnull
40 IL_0035: مكافئ
41 IL_0037: stloc.s CS$ 4 0000 دولار
42 IL_0039: ldloc.s CS$ 4 0000 دولار
43 IL_003b: brtrue.s IL_0044
44 إيل_003د: لدولوك 1
45 IL_003e: مثيل callvirt باطل [mscorlib]System.IDisposable::Dispose()
46 إيل_0043: لا
47 IL_0044: أخيرًا
48 } // معالج النهاية
49
عبارة الاستخدام لها نفس التأثير لتحرير موارد الكائنات غير المُدارة. غالبًا ما يتم مواجهة ذلك في المقابلات، مثل ما هي استخدامات الكلمة الرئيسية والأسئلة المشابهة. الإجابة المثالية الأساسية هي أنه بالإضافة إلى الإشارة إلى مساحة الاسم وتعيين الأسماء المستعارة لمساحة الاسم، فإن هذا الاستخدام يحقق إعادة تدوير موارد الكائنات غير المُدارة مثل حظر المحاولة أخيرًا. مجرد طريقة بسيطة لكتابتها.
عند استخدام أسلوب التخلص لتحرير كائن غير مُدار، يجب عليك الاتصال بـ GC.SuppressFinalize. إذا كان الكائن في قائمة انتظار الإنهاء، فسيمنع GC.SuppressFinalize GC من استدعاء أسلوب الإنهاء. لأن استدعاء طريقة الإنهاء سيضحي ببعض الأداء. إذا كانت طريقة التخلص الخاصة بك قد قامت بالفعل بتنظيف الموارد المفوضة، فلن تكون هناك حاجة إلى GC لاستدعاء طريقة الإنهاء للكائن (MSDN) مرة أخرى. مرفق رمز MSDN للرجوع إليه.
شفرة
الفئة العامة BaseResource: IDisposable
{
// أشر إلى الموارد الخارجية غير المُدارة
مقبض IntPtr الخاص؛
// الموارد المُدارة الأخرى التي تستخدمها هذه الفئة.
مكونات المكونات الخاصة؛
// تتبع ما إذا كان يتم استدعاء طريقة .Dispose، أو بت الإشارة، أو التحكم في سلوك أداة تجميع البيانات المهملة
تم التخلص من المنطق الخاص = خطأ؛
//المنشئ
المصدر الأساسي العام ()
{
// أدخل رمز المنشئ المناسب هنا.
}
// تنفيذ الواجهة IDisposable.
// لا يمكن الإعلان عنها كطريقة افتراضية virtual.
// لا يمكن للفئات الفرعية تجاوز هذه الطريقة.
التخلص من الفراغ العام ()
{
تخلص (صحيح) ؛
// اترك قائمة انتظار الإنهاء
// قم بتعيين رمز Finalizer لحظر الكائن
//
GC.SuppressFinalize(هذا);
}
// يتم تنفيذ التخلص (التخلص المنطقي) في حالتين مختلفتين.
// إذا كان التصرف مساويا للحقيقة، فقد تم استدعاء الطريقة
// أو يمكن استدعاؤها بشكل غير مباشر بواسطة رمز المستخدم
// إذا كان التخلص يساوي خطأ، فقد تم استدعاء الطريقة داخليًا بواسطة أداة الإنهاء،
// لا يمكنك الرجوع إلى كائنات أخرى، بل يمكن تحرير الموارد غير المُدارة فقط.
التخلص من الفراغ الظاهري المحمي (التخلص المنطقي)
{
// تحقق مما إذا تم استدعاء التخلص.
إذا (! هذا .تخلص)
{
// إذا كان يساوي صحيحًا، فقم بتحرير جميع الموارد المُدارة وغير المُدارة
إذا (تخلص)
{
// حرر الموارد المُدارة.
Components.Dispose();
}
// حرر الموارد غير المُدارة، إذا كان التخلص منها خطأً،
// سيتم تنفيذ التعليمات البرمجية التالية فقط.
CloseHandle(handle);
Handle = IntPtr.Zero;
// لاحظ أن هذا ليس آمنًا لمؤشر الترابط.
// بعد تحرير المورد المُدار، يمكن بدء سلاسل رسائل أخرى لتدمير الكائن.
// ولكن قبل تعيين العلامة التي تم التخلص منها على "صحيح".
// إذا كانت سلامة الخيط مطلوبة، فيجب على العميل تنفيذها.
}
التخلص = صحيح؛
}
// استخدم التشغيل المتداخل لاستدعاء الطريقة
// مسح الموارد غير المُدارة.
[System.Runtime.InteropServices.DllImport( "Kernel32")]
Boolean CloseHandle خارجي خاص ثابت (مقبض IntPtr) ؛
// استخدم C# destructor لتنفيذ كود Finalizer
// لا يمكن استدعاء هذا وتنفيذه إلا إذا لم يتم استدعاء طريقة التخلص.
// إذا أعطيت الفصل الأساسي فرصة للانتهاء.
// لا توفر أداة تدمير للفئات الفرعية.
~الموارد الأساسية ()
{
// لا تقم بإعادة إنشاء رمز التنظيف.
// بناءً على اعتبارات الموثوقية وقابلية الصيانة، فإن استدعاء Dispose(false) هو أفضل طريقة
تخلص (خطأ) ؛
}
// يسمح لك باستدعاء طريقة التخلص عدة مرات،
// ولكن سيتم طرح استثناء إذا تم تحرير الكائن.
// بغض النظر عن وقت معالجة الكائن، ستتحقق مما إذا كان الكائن قد تم تحريره أم لا.
// تحقق لمعرفة ما إذا تم التخلص منها.
الفراغ العام DoSomething ()
{
إذا (هذا .التخلص)
{
رمي ObjectDisposeException();
}
}
بالنسبة للأنواع التي يكون فيها استدعاء أسلوب Close أكثر طبيعية من أسلوب Dispose، يمكنك إضافة أسلوب Close إلى الفئة الأساسية.
لا تأخذ الطريقة Close أي معلمات وتستدعي طريقة التخلص التي تقوم بإجراء عملية التنظيف المناسبة.
يوضح المثال التالي الأسلوب Close.
// لا تقم بتعيين الطريقة على الافتراضية.
// لا يُسمح للفئات الموروثة بتجاوز هذه الطريقة
إغلاق الفراغ العام ()
{
// استدعاء معلمة التخلص بدون معلمات.
التخلص();
}
الفراغ العام الثابت الرئيسي ()
{
// أدخل الكود هنا للإنشاء
// واستخدم كائن BaseResource.
}