تعدد. نموذج ذاكرة Java (الجزء الأول)

مرحبا يا هبر! أقدم انتباهكم إلى ترجمة الجزء الأول من مقالة "Java Memory Model" لجاكوب جينكوف.



أنا أتدرب على التدريب في Java وأحتاج لدراسة مقالة Java Memory Model . قمت بترجمتها لفهم أفضل ، ولكن لكي لا تضيع الخير ، قررت مشاركتها مع المجتمع. أعتقد أنها ستكون مفيدة للمبتدئين ، وإذا أحبها شخص ما ، فسأترجم الباقي.



لم يكن نموذج ذاكرة Java الأصلي جيدًا ، لذلك تم تعديله في Java 1.5. هذا الإصدار لا يزال قيد الاستخدام اليوم (Java 14+).





نموذج ذاكرة Java الداخلية



يقسم نموذج ذاكرة Java المستخدم داخليًا بواسطة JVM الذاكرة إلى كومة خيط وكومة. يوضح هذا الرسم التخطيطي نموذج ذاكرة Java من وجهة نظر منطقية:



صورة



لكل خيط يتم تشغيله في جهاز Java الظاهري مكدس خاص به. يحتوي المكدس على معلومات حول الأساليب التي استدعاها مؤشر الترابط للوصول إلى نقطة التنفيذ الحالية. سوف أشير إلى هذا على أنه "مكدس المكالمة". بمجرد تنفيذ مؤشر الترابط الخاص به ، يتغير مكدس الاستدعاء.



يحتوي رصة مؤشر الترابط على جميع المتغيرات المحلية لكل طريقة يتم تنفيذها (جميع الطرق في رصة الاستدعاء). يمكن لمؤشر الترابط الوصول فقط إلى المكدس الخاص به. تكون المتغيرات المحلية غير مرئية لجميع سلاسل الرسائل الأخرى باستثناء سلسلة المحادثات التي أنشأتها. حتى إذا كان هناك موضوعان ينفذان نفس الرمز ، فسيظلان ينشئان متغيرات محلية لهذا الرمز على مكدساتهما الخاصة. وبالتالي ، كل خيط لديه نسخته الخاصة لكل متغير محلي.



يتم تخزين جميع المتغيرات المحلية من الأنواع البدائية (منطقية ، بايت ، قصيرة ، تشار ، int ، طويلة ، عائمة ، مزدوجة) بالكامل على رصة الخيط ولا تكون مرئية للخيوط الأخرى. يمكن لمؤشر ترابط واحد تمرير نسخة من متغير بدائي إلى مؤشر ترابط آخر ، ولكن لا يمكن مشاركة متغير محلي بدائي.



يحتوي الكومة على كافة الكائنات التي تم إنشاؤها في تطبيق Java الخاص بك ، بغض النظر عن مؤشر الترابط الذي أنشأ الكائن. يتضمن ذلك إصدارات من الكائنات ذات الأنواع البدائية (مثل Byte ، Integer ، Long ، إلخ.). لا يهم إذا تم إنشاء الكائن وتخصيصه لمتغير محلي أو تم إنشاؤه كمتغير عضو لكائن آخر ، فسيتم تخزينه في الكومة.



فيما يلي رسم تخطيطي يوضح مكدس الاستدعاء والمتغيرات المحلية المخزنة على مكدسات سلسلة الرسائل ، بالإضافة إلى الكائنات المخزنة في كومة الذاكرة المؤقتة:



صورة



يمكن أن يكون المتغير المحلي من النوع البدائي ، وفي هذه الحالة يتم تخزينه بالكامل على مكدس مؤشر الترابط.



يمكن أن يكون المتغير المحلي أيضًا مرجع كائن. في هذه الحالة ، يتم تخزين المرجع (المتغير المحلي) على مكدس مؤشر الترابط ، ولكن يتم تخزين الكائن نفسه في كومة الذاكرة المؤقتة.



يمكن أن يحتوي الكائن على طرق ، ويمكن أن تحتوي هذه الطرق على متغيرات محلية. يتم أيضًا تخزين هذه المتغيرات المحلية على مكدس مؤشر الترابط ، حتى إذا تم تخزين الكائن الذي يملك الطريقة على كومة الذاكرة المؤقتة.



يتم تخزين متغيرات عضو كائن على كومة الذاكرة المؤقتة مع الكائن نفسه. هذا صحيح عندما يكون متغير العضو من النوع البدائي وعندما يكون مرجع كائن.



يتم أيضًا تخزين متغيرات فئة ثابتة على كومة الذاكرة المؤقتة مع تعريف الفئة.



يمكن الوصول إلى الكائنات في كومة الذاكرة المؤقتة بواسطة كافة مؤشرات الترابط التي لها مرجع إلى الكائن. عندما يكون لمؤشر الترابط حق الوصول إلى كائن ما ، يمكنه أيضًا الوصول إلى متغيرات الأعضاء الخاصة بهذا الكائن. إذا استدعت خيطتان طريقة على نفس الكائن في نفس الوقت ، فسيتمكن كلاهما من الوصول إلى متغيرات عضو الكائن ، ولكن سيكون لكل خيط نسخة خاصة به من المتغيرات المحلية.



فيما يلي رسم تخطيطي يوضح النقاط أعلاه: يحتوي



صورة



خيطان على مجموعة من المتغيرات المحلية. يشير المتغير المحلي 2 إلى كائن مشترك في كومة الذاكرة المؤقتة (الكائن 3). بمعنى أن كل سلسلة من سلاسل العمليات لها نسختها الخاصة من المتغير المحلي مع مرجعها الخاص. وبالتالي ، يشير مرجعان مختلفان إلى نفس الكائن في الكومة.



لاحظ أن الكائن 3 العام يحتوي على مراجع للكائن 2 والكائن 4 كمتغيرات للأعضاء (تظهر بالسهام). من خلال هذه الروابط ، يمكن لخيوطين الوصول إلى



الهدف 2 والهدف 4. ويظهر الرسم التخطيطي أيضًا المتغير المحلي (المتغير المحلي 1). تحتوي كل نسخة منه على مراجع مختلفة تشير إلى كائنين مختلفين (الكائن 1 والكائن 5) ، وليس إلى نفس الكائن. من الناحية النظرية ، يمكن لكلا الخيوط الوصول إلى كل من الكائن 1 والكائن 5 إذا كان لديهم مراجع لكل من هذه الكائنات. ولكن في الرسم البياني أعلاه ، يشير كل خيط فقط إلى أحد الجسمين.



إذن ما هو نوع رمز Java الذي قد يكون نتيجة هذه الرسوم التوضيحية؟ حسنًا ، كود بسيط مثل الرمز أدناه:



Public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;

        MySharedObject localVariable2 =
            MySharedObject.sharedInstance;

        //... do more with local variables.

        methodTwo();
    }

    public void methodTwo() {
        Integer localVariable1 = new Integer(99);

        //... do more with local variable.
    }
}


public class MySharedObject {

    // ,    MySharedObject

    public static final MySharedObject sharedInstance =
        new MySharedObject();


    // -,      

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member2 = 67890;
}


يستدعي الأسلوب run () methodOne () ويستدعي methodOne () methodTwo ().



يعلن methodOne () عن متغير محلي بدائي (localVariable1) من النوع int ومتغير محلي (localVariable2) يمثل مرجع كائن.



سيقوم كل مؤشر ترابط ينفذ الأسلوب One () بإنشاء نسخته الخاصة من localVariable1 و localVariable2 على مكدساتها الخاصة. ستكون المتغيرات localVariable1 منفصلة تمامًا عن بعضها البعض ، حيث يتم وضعها في رصة كل سلسلة رسائل. لا يمكن لمؤشر ترابط واحد رؤية التغييرات التي يجريها مؤشر ترابط آخر على نسخته من localVariable1.



يقوم كل مؤشر ترابط ينفذ الأسلوب One () أيضًا بإنشاء نسخته الخاصة من localVariable2. ومع ذلك ، ينتهي نسختين مختلفتين من localVariable2 للإشارة إلى نفس الكائن في كومة الذاكرة المؤقتة. النقطة هي أن localVariable2 يشير إلى الكائن المشار إليه بواسطة المتغير الثابت sharedInstance. توجد نسخة واحدة فقط من المتغير الثابت ويتم تخزين هذه النسخة في كومة الذاكرة المؤقتة. وبالتالي ، ينتهي كلا النسختين من localVariable2 بالإشارة إلى نفس مثيل MySharedObject. يتم تخزين مثيل MySharedObject أيضًا في كومة الذاكرة المؤقتة. وهي تقابل الهدف 3 في الرسم البياني أعلاه.



لاحظ أن فئة MySharedObject تحتوي أيضًا على متغيرين للأعضاء. يتم تخزين متغيرات العضو نفسها في كومة الذاكرة المؤقتة مع الكائن. يشير المتغيران العضوان إلى كائنين صحيحين آخرين. تتطابق هذه الكائنات الصحيحة مع الهدف 2 والهدف 4 في الرسم التخطيطي.



لاحظ أيضًا أن methodTwo () ينشئ متغيرًا محليًا باسم localVariable1. هذا المتغير المحلي هو مرجع لكائن عدد صحيح. يعين الأسلوب المرجع localVariable1 للإشارة إلى مثيل Integer جديد. سيتم تخزين الرابط في نسخته الخاصة من localVariable1 لكل خيط. سيتم تخزين مثيلين عدد صحيح على كومة الذاكرة المؤقتة ، ولأن الطريقة تقوم بإنشاء كائن عدد صحيح جديد في كل مرة يتم تنفيذه ، ستنشئ سلاسل العمليات التي تنفذ هذه الطريقة مثيلات عدد صحيح منفصلة. وهي تتوافق مع الهدف 1 والهدف 5 في الرسم البياني أعلاه.



لاحظ أيضًا المتغيرين العضوين في فئة MySharedObject من النوع الطويل ، وهو نوع بدائي. نظرًا لأن هذه المتغيرات هي متغيرات أعضاء ، فلا تزال مخزنة في كومة الذاكرة المؤقتة مع الكائن. يتم تخزين المتغيرات المحلية فقط على مكدس مؤشر الترابط.



الجزء 2 هنا.



All Articles