أحجام أنواع مختلفة من كائنات Java

مقدمة


هل يحتوي كائن Java على:

  • الحقول المعلنة في الطبقة الفائقة؟
  • الحقول الخاصة المعلنة في فئة؟
  • الأساليب؟
  • عناصر مجموعة؟
  • طول مجموعة؟
  • كائن آخر (في حد ذاته)؟
  • رمز التجزئة؟
  • اكتب (الخاصة)؟
  • اسم (الخاصة)؟

يمكن الحصول على إجابات لهذه الأسئلة (وغيرها) باستخدام مكتبة الفئة org.openjdk.jol والتي ، على وجه الخصوص ، تسمح لنا بفهم أن الكائن عبارة عن منطقة ذاكرة:

  • تشمل ما يلي:
    • الرأس (حتى 16 بايت) ، وفيه:
      • رمز التجزئة
      • اكتب المرجع
      • طول الصفيف (للصفيف)
    • جميع الحقول (بما في ذلك القطاع الخاص) المعلنة في جميع الفئات الفائقة
    • أو عناصر الصفيف (لصفيف)
  • لا تحتوي على:
    • متغيرات ثابتة
    • طرق
    • أشياء أخرى في نفسك
    • الاسم الخاص (أي ، الكائن ليس له اسم)


تدريب


فيما يلي نتائج تقييم ذاكرة الكائنات من أنواع مختلفة بالطريقة من وصف الحزمة java.lang.instrument (انظر أيضًا هنا ). تسمح لنا هذه النتائج بالإجابة على معظم الأسئلة المطروحة أعلاه.

يجب إتمام الخطوات التالية:

  1. قم بإنشاء فئة وكيل تحتوي على طريقة premain:
    public static void premain(String, Instrumentation) {...} 
  2. قم بإنشاء أرشيف يحتوي على فئة الوكيل وملف البيان مع المحتويات:
     Premain-class: -- 
  3. إنشاء فئة قابلة للتنفيذ لتقييم الذاكرة.
  4. حدد الأرشيف باستخدام المعلمة "-javaagent" عند بدء تشغيل الجهاز الظاهري:
     java -javaagent:- -- 


لنبدأ بحالة اختبار. للبساطة نستخدم حزمة غير محددة.

الخطوة 1. إنشاء فئة وكيل التجريبية


 import java.lang.instrument.Instrumentation; public class A { public static void premain(String notUsedHere, Instrumentation i) { System.out.println("premain"); } } 

نحن نجمع:

 javac A.java 

الخطوة 2. إنشاء ملف بيان m.txt يحتوي على:


 Premain-class: A   

تنبيه: يجب أن يكون السطر الثاني من الملف خاليًا ، وليس يحتوي على مسافات.

إنشاء أرشيف A.jar:

 jar cmf m.txt A.jar A.class 

الخطوة 3. إنشاء فئة قابلة للتنفيذ التجريبية


 public class M { public static void main(String[] notUsedHere) { //     System.out.println("main"); } } 

نحن نجمع:

 javac M.java 

الخطوة 4. أداء


 java -javaagent:A.jar M 

النتيجة:
 premain main 

يشير إلى أن الطريقة الرئيسية لفئة الوكيل كانت تسمى أولاً ، ثم الطريقة الرئيسية للفئة القابلة للتنفيذ.

الآن قم بإنشاء فئة الوكيل المطلوبة:

 import java.lang.instrument.Instrumentation; public class A { //      private static Instrumentation ins; public static void premain(String notUsedHere, Instrumentation i) { ins = i; } public static Instrumentation instrumentation() {return ins;} } 

والفئة القابلة للتنفيذ:

 class M { public static void main(String[] notUsedHere) { mem("Object", new Object()); } private static void mem(Object o, Object ref) { System.out.println(o + ": " + objectBytesEstimate(ref)); } private static long objectBytesEstimate(Object ref) { if (A.instrumentation() == null) { throw new RuntimeException("Not initialized instrumentation."); } return A.instrumentation().getObjectSize(ref); } } 

طريقة

 long getObjectSize(Object --) 

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

نقوم بها:

 javac *.java jar cmf m.txt A.jar A.class java -javaagent:A.jar M 

ونحصل على النتيجة:

 Object: 16 

يظهر أن كائن EMPTY من النوع كائن يشغل هنا (BY ASSESSMENT) 16 بايت. من هذه ، 12 بايت تشغل الرأس ، و 4 بايت في النهاية تعمل على محاذاة طول الكائن إلى حدود 8 بايت.

النتائج


سوف تحتوي الأمثلة الأخرى فقط على الكود الموحد في الطريقة الرئيسية للفئة M. وينبغي تنفيذها لكل مثال باستخدام الأوامر:

 javac M.java java -javaagent:A.jar M 

إعادة إنشاء A.jar ليست ضرورية.

على سبيل المثال ، للحصول على تقدير لحجم ذاكرة كائن من النوع التعسفي بدون حقول ، نضع الشفرة في الطريقة الرئيسية:

 class C {}; mem("Empty", new C()); // Empty: 16 

توضح النتيجة المشار إليها في التعليق أن كائنًا بدون حقول يشغل عددًا من وحدات البايت مثل كائن من النوع كائن.

علاوة على ذلك ، نتيجة البرنامج:

 {class C {int a; } mem(1, new C());} // 1: 16 {class C {int a,b; } mem(2, new C());} // 2: 24 {class C {int a,b,c; } mem(3, new C());} // 3: 24 {class C {int a,b,c,d;} mem(4, new C());} // 4: 32 

يشير إلى أن كل حقل int يأخذ 4 بايت. ألاحظ أن كل سطر هنا عبارة عن كتلة منفصلة ، والتي تتيح لك استخدام نفس الاسم لفئات مختلفة.

كل حقل طويل 8 بايت:

 {class C {long a; } mem(1, new C());} // 1: 24 {class C {long a,b; } mem(2, new C());} // 2: 32 {class C {long a,b,c;} mem(3, new C());} // 3: 40 

يأخذ كل حقل منطقي 1 بايت (لهذا VM):

 {class C {boolean a; } mem(1, new C());} // 1: 16 {class C {boolean a,b; } mem(2, new C());} // 2: 16 {class C {boolean a,b,c; } mem(3, new C());} // 3: 16 {class C {boolean a,b,c,d; } mem(4, new C());} // 4: 16 {class C {boolean a,b,c,d,e;} mem(5, new C());} // 5: 24 

يستغرق كل حقل مرجعي 4 بايت (لهذا الجهاز الظاهري):

 {class C {Boolean a; } mem(1, new C());} // 1: 16 {class C {Integer a; } mem(1, new C());} // 1: 16 {class C {Long a; } mem(1, new C());} // 1: 16 {class C {C a; } mem(1, new C());} // 1: 16 {class C {Boolean a,b; } mem(2, new C());} // 2: 24 {class C {Integer a,b; } mem(2, new C());} // 2: 24 {class C {Long a,b; } mem(2, new C());} // 2: 24 {class C {C a,b; } mem(2, new C());} // 2: 24 {class C {Boolean a,b,c; } mem(3, new C());} // 3: 24 {class C {Integer a,b,c; } mem(3, new C());} // 3: 24 {class C {Long a,b,c; } mem(3, new C());} // 3: 24 {class C {C a,b,c; } mem(3, new C());} // 3: 24 {class C {Boolean a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Integer a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Long a,b,c,d;} mem(4, new C());} // 4: 32 {class C {C a,b,c,d;} mem(4, new C());} // 4: 32 

يأخذ حقل نوع السلسلة أيضًا 4 بايتات ، مثل كل مرجع:

 {class C {String a; } mem(" null", new C());} // null: 16 {class C {String a=""; } mem(" empty", new C());} // empty: 16 {class C {String a="A"; } mem("1-char", new C());} // 1-char: 16 {class C {String a="1234567";} mem("7-char", new C());} // 7-char: 16 

يأخذ حقل مرجع الصفيف أيضًا 4 بايت ، مثل كل مرجع:

 {class C {int[] a; } mem("null", new C());} // null: 16 {class C {int[] a = {}; } mem(" 0", new C());} // 0: 16 {class C {int[] a = new int[1]; } mem(" 1", new C());} // 1: 16 {class C {int[] a = new int[7]; } mem(" 7", new C());} // 7: 16 {class C {int[][] a = {}; } mem(" 00", new C());} // 00: 16 {class C {int[][] a = new int[1][1];} mem(" 11", new C());} // 11: 16 {class C {int[][] a = new int[7][7];} mem(" 77", new C());} // 77: 16 

يحتوي الكائن الفرعي على كل حقل تم الإعلان عنه في الفئة الفائقة ، بغض النظر عن معدل الوصول:

 {class S { } class C extends S {long a;} mem("0+1", new C());} // 0+1: 24 {class S {private long a;} class C extends S { } mem("1+0", new C());} // 1+0: 24 

يحتوي الكائن الفرعي على حقل تم الإعلان عنه في الفئة الفائقة بنفس الاسم كما في الفئة الفرعية (ما يسمى مخفي - مخفي):

 {class S { } class C extends S {long a,b;} mem("0+2", new C());} // 0+2: 32 {class S {long a;} class C extends S {long a; } mem("1+1", new C());} // 1+1: 32 

يحتوي الكائن الفرعي على كل حقل تم تعريفه في كل فئة من الفئات الفائقة:

 class U {private long a; } class S extends U {private long a; } class C extends S { long a; } mem("1+1+1", new C()); // 1+1+1: 40 class D { long a,b,c;} mem("0+0+3", new D()); // 0+0+3: 40 

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

 {long[] a = new long[ 0]; mem(" 0", a);} // 0: 16 {long[] a = new long[ 1]; mem(" 1", a);} // 1: 24 {long[] a = new long[ 2]; mem(" 2", a);} // 2: 32 {long[] a = new long[ 3]; mem(" 3", a);} // 3: 40 {long[] a = new long[100]; mem("100", a);} // 100: 816 

وللصفيف من الروابط:

 {Long[] a = new Long[ 0]; mem(" 0", a);} // 0: 16 {Long[] a = new Long[ 1]; mem(" 1", a);} // 1: 24 {Long[] a = new Long[ 2]; mem(" 2", a);} // 2: 24 {Long[] a = new Long[ 3]; mem(" 3", a);} // 3: 32 {Long[] a = new Long[100]; mem("100", a);} // 100: 416 

الآن ، بدافع الفضول ، نقوم بمقارنة أحجام العديد من الكائنات من أنواع مختلفة:

 mem(" Object", new Object()); // Object: 16 mem(" String", new String("ABC")); // String: 24 mem(" Exception", new Exception()); // Exception: 40 mem(" int.class", int.class); // int.class: 112 mem(" int[].class", int[].class); // int[].class: 112 mem("Object.class", Object.class); // Object.class: 112 mem("System.class", System.class); // System.class: 160 mem("String.class", String.class); // String.class: 136 

نفس الشيء بالنسبة jdk مختلفة على معالج 64 بت:

                 jdk1.6.0_45 jdk1.7.0_80 jdk1.8.0_191 jdk-9 jdk-12 jdk-13
                 ----------- ----------- ------------ ------ ------ ---- -
       الهدف: 16 16 16 16 16 16
       السلسلة: 32 24 24 24 24 24
    استثناء: 32 32 32 40 40 40
    الطبقة: 104 88 104 112 104 112
  int [] class: 584 544 480 112 104 112
 فئة الكائن: 600 560 496 112 104 112
 فئة النظام: 624 560 496 144 152 160
 السلسلة: 696 640 624 136 128 136 

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

في الختام ، تذكير: يتم تقدير جميع البيانات على حجم الكائن ويمكن أن تختلف إلى حد ما من تنفيذ إلى آخر ، وبالطبع ، لأجهزة افتراضية مختلفة.

Source: https://habr.com/ru/post/ar470441/


All Articles