JVM Internals، Part 2 - Class File Structure

مرحبا بالجميع! تم إعداد ترجمة للمقال خصيصًا لطلاب دورة Java Developer .




نواصل الحديث عن كيفية عمل Java Virtual Machine داخليًا. في المقالة السابقة ( الأصل باللغة الإنجليزية ) ، درسنا النظام الفرعي لتحميل الصف. في هذه المقالة سوف نتحدث عن هيكل الملفات الصفية.

كما نعلم بالفعل ، يتم تجميع جميع الشفرات المصدرية المكتوبة بلغة برمجة Java أولاً إلى رمز javac باستخدام برنامج التحويل البرمجي javac ، الذي يعد جزءًا من Java Development Kit. يتم تخزين bytecode في ملف ثنائي في ملف فئة خاصة. ثم يتم تحميل ملفات الفئة هذه ديناميكيًا (إذا لزم الأمر) في الذاكرة بواسطة أداة تحميل الفئة (ClassLoader).


الشكل - تجميع شفرة مصدر جافا

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

ملاحظة - للبساطة ، سيتم .class الملفات ذات الملحق .class "ملفات فئة".

دعنا نكتب برنامج بسيط.

 public class ClassOne{ public static void main(String[] args){ System.out.println("Hello world"); } static class StaticNestedClass{ } } class ClassTwo{ } interface InterfaceOne{ } 

يؤدي تشغيل javac لهذا الملف إلى الملفات التالية.

 ClassOne$StaticNestedClass.class ClassOne.class ClassTwo.class InterfaceOne.class 

كما ترون ، يتم إنشاء ملف فئة منفصلة لكل فئة وواجهة.

ما هو داخل ملف الفصل؟


ملف الفصل في تنسيق ثنائي. عادة ما يتم كتابة المعلومات الموجودة فيه دون المسافة البادئة بين أجزاء متتالية من المعلومات ، كل شيء محاذي للحدود بايت. تتم كتابة جميع قيم 16 بت و 32 بت باستخدام اثنين أو أربعة بايت 8 بت متتالية.

يحتوي ملف الفصل على المعلومات التالية.

رقم السحر ، التوقيع . البايت الأربع الأولى من كل ملف فئة دائماً 0xCAFEBABE . تحدد هذه البايتات الأربعة ملف فئة Java.

نسخة الملف. تحتوي البايتات الأربعة التالية على الإصدارات الرئيسية والثانوية من الملف. تحدد هذه الأرقام معًا إصدار تنسيق ملف الفصل. إذا كان ملف الفصل يحتوي على إصدار رئيسي رئيسي من M و m ثانوي ، فسنقوم بتعيين هذا الإصدار على أنه Mm

كل JVM لها قيود على الإصدارات المدعومة من ملفات الفئة. على سبيل المثال ، يدعم Java 11 الإصدارات الرئيسية من 45 إلى 55 ، و Java 12 - من 45 إلى 56.

مجموعة من الثوابت. جدول بنيات تمثل ثوابت السلسلة وأسماء الفئات والواجهات والحقول والأساليب والثوابت الأخرى الموجودة في بنية ClassFile وبنيتها الأساسية. يبدأ كل عنصر تجمع ثابت بعلامة أحادية البايت تحدد نوع الثابت. اعتمادًا على نوع الثابت ، قد تكون البايتات التالية قيمة ثابتة فورية أو مرجعًا إلى عنصر آخر في التجمع.

أعلام الوصول. قائمة العلامات التي تشير إلى الفصل هي إما واجهة ، عامة أو خاصة ، فئة نهائية أم لا. يتم وصف إشارات مختلفة مثل ACC_PUBLIC و ACC_FINAL و ACC_INTERFACE و ACC_ENUM ، إلخ في مواصفات Java Virtual Machine.

هذه الفئة. رابط إلى الإدخال في التجمع المستمر.

الطبقة العليا. رابط إلى الإدخال في التجمع المستمر.

الواجهات. عدد واجهات تنفيذها من قبل الطبقة.

عدد الحقول. عدد الحقول في الفصل أو الواجهة.

المجال. بعد عدد الحقول ، يتبع جدول بنيات ذات طول متغير. واحد لكل حقل مع وصف لنوع الحقل واسم (مع الإشارة إلى مجموعة الثوابت).

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

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

عدد الصفات. عدد السمات في هذه الفئة أو الواجهة أو الوحدة النمطية.

الصفات. عدد السمات يتبعه جداول أو هياكل متغيرة الطول تصف كل سمة. على سبيل المثال ، هناك دائمًا سمة "SourceFile". يحتوي على اسم الملف المصدر الذي تم تحويل ملف الفئة منه.

على الرغم من أن ملف الفصل الدراسي لا يمكن قراءته مباشرة من قبل الإنسان ، إلا أن هناك أداة في JDK تسمى javap تعرض محتوياته بتنسيق مناسب.

دعنا نكتب برنامج جافا بسيط كما هو موضح أدناه.

 package bytecode; import java.io.Serializable; public class HelloWorld implements Serializable, Cloneable { public static void main(String[] args) { System.out.println("Hello World"); } } 

دعنا javap هذا البرنامج مع javac ، والذي سينشئ ملف HelloWorld.class ، ونستخدم javap لعرض ملف HelloWorld.class . يؤدي تشغيل javap باستخدام الخيار -v (verbose) لـ HelloWorld.class إلى النتيجة التالية:

 Classfile /Users/apersiankite/Documents/code_practice/java_practice/target/classes/bytecode/HelloWorld.class Last modified 02-Jul-2019; size 606 bytes MD5 checksum 6442d93b955c2e249619a1bade6d5b98 Compiled from "HelloWorld.java" public class bytecode.HelloWorld implements java.io.Serializable,java.lang.Cloneable minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // bytecode/HelloWorld super_class: #6 // java/lang/Object interfaces: 2, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #6.#22 // java/lang/Object."<init>":()V #2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #25 // Hello World #4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #28 // bytecode/HelloWorld #6 = Class #29 // java/lang/Object #7 = Class #30 // java/io/Serializable #8 = Class #31 // java/lang/Cloneable #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 LocalVariableTable #14 = Utf8 this #15 = Utf8 Lbytecode/HelloWorld; #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String;)V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String; #20 = Utf8 SourceFile #21 = Utf8 HelloWorld.java #22 = NameAndType #9:#10 // "<init>":()V #23 = Class #32 // java/lang/System #24 = NameAndType #33:#34 // out:Ljava/io/PrintStream; #25 = Utf8 Hello World #26 = Class #35 // java/io/PrintStream #27 = NameAndType #36:#37 // println:(Ljava/lang/String;)V #28 = Utf8 bytecode/HelloWorld #29 = Utf8 java/lang/Object #30 = Utf8 java/io/Serializable #31 = Utf8 java/lang/Cloneable #32 = Utf8 java/lang/System #33 = Utf8 out #34 = Utf8 Ljava/io/PrintStream; #35 = Utf8 java/io/PrintStream #36 = Utf8 println #37 = Utf8 (Ljava/lang/String;)V { public bytecode.HelloWorld(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 4: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lbytecode/HelloWorld; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 7: 0 line 8: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "HelloWorld.java" 

هنا يمكنك أن ترى أن الفصل عام وله 37 إدخال في المجموعة الثابتة. هناك سمة واحدة (SourceFile أدناه) ، تنفذ الفئة واجهات اثنين (Serializable ، Cloneable) ، وليس لديها حقول ، وهناك طريقتان.

ربما لاحظت وجود طريقة رئيسية ثابتة واحدة فقط في التعليمات البرمجية المصدر ، ولكن يشير ملف الفصل إلى وجود طريقتين. تذكر المُنشئ الافتراضي - هذا مُنشئ بلا وسيطة يُضاف من قِبل برنامج التحويل البرمجي javac ، والذي يكون رمزه الثانوي مرئيًا أيضًا في الإخراج. البنائين تعتبر أساليب.

يمكنك قراءة المزيد عن japap هنا .

نصيحة : يمكنك أيضًا استخدام javap لترى كيف تختلف lambdas عن الطبقات الداخلية المجهولة.

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


All Articles