जावा बाइटकोड फंडामेंटल

जावा डेवलपर्स को आमतौर पर एक वर्चुअल मशीन में चलने वाले बायटेकोड के बारे में जानने की जरूरत नहीं होती है, लेकिन जो लोग आधुनिक फ्रेमवर्क, कंपाइलर या यहां तक ​​कि जावा टूल विकसित करते हैं, उन्हें बायटेकोड को समझने की आवश्यकता हो सकती है और शायद यह भी समझें कि इसका उपयोग कैसे करें। अपने स्वयं के प्रयोजनों के लिए। इस तथ्य के बावजूद कि ASM, cglib, Javassist जैसे विशेष पुस्तकालय बायटेकोड का उपयोग करने में मदद करते हैं, इन पुस्तकालयों को प्रभावी ढंग से उपयोग करने के लिए मूल बातें की समझ आवश्यक है।

लेख में उन मूल बातों का वर्णन किया गया है जिनसे आप इस विषय (लगभग प्रति) को खोदने पर निर्माण कर सकते हैं।



आइए एक साधारण उदाहरण से शुरू करें, जिसका नाम है एक पीओजेओ एक क्षेत्र और इसके लिए एक गेट और सेटर।

public class Foo { private String bar; public String getBar(){ return bar; } public void setBar(String bar) { this.bar = bar; } }
      
      





जब आप javac Foo.java कमांड का उपयोग करके कक्षा को संकलित करते हैं, तो आपको Foo.class फ़ाइल दिखाई देगी जिसमें बायटेकोड होगा। यहाँ हेक्स संपादक में इसकी सामग्री कैसी है:



छवि



हेक्साडेसिमल नंबरों (बाइट्स) की प्रत्येक जोड़ी को ओपकोड्स (मेनेमिक्स) में अनुवादित किया गया है। इसे द्विआधारी प्रारूप में पढ़ने की कोशिश करना क्रूर होगा। आइए आगे बढ़ने के लिए मेनेमोनिक प्रतिनिधित्व करें।



Javap -c Foo कमांड बाईटकोड को आउटपुट करेगा:

 public class Foo extends java.lang.Object { public Foo(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public java.lang.String getBar(); Code: 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn public void setBar(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #2; //Field bar:Ljava/lang/String; 5: return }
      
      







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



इसके अलावा, बाइट-कोड निर्देशों का अध्ययन (हमारे पास aload_0 और aload_1 है), हम देखते हैं कि उनमें से कुछ में aload_0 और istore_2 जैसे उपसर्ग हैं। यह डेटा के प्रकार को संदर्भित करता है जिसके साथ निर्देश संचालित होता है। उपसर्ग "ए" का मतलब है कि ओपकोड ऑब्जेक्ट के संदर्भ को नियंत्रित करता है। "I", क्रमशः, पूर्णांक को नियंत्रित करता है।



यहाँ एक दिलचस्प बात यह है कि कुछ निर्देश # 1 और # 2 के अजीब ऑपरेंडों पर काम करते हैं, जो वास्तव में वर्ग के सहकर्मियों के पूल को संदर्भित करता है। क्लास फाइल पर करीब से नज़र डालने का समय आ गया है। Javap -c -s -verbose (-s को हस्ताक्षर दिखाने के लिए, -verbose to verbose output)

 Compiled from "Foo.java" public class Foo extends java.lang.Object SourceFile: "Foo.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #4.#17; // java/lang/Object."":()V const #2 = Field #3.#18; // Foo.bar:Ljava/lang/String; const #3 = class #19; // Foo const #4 = class #20; // java/lang/Object const #5 = Asciz bar; const #6 = Asciz Ljava/lang/String;; const #7 = Asciz ; const #8 = Asciz ()V; const #9 = Asciz Code; const #10 = Asciz LineNumberTable; const #11 = Asciz getBar; const #12 = Asciz ()Ljava/lang/String;; const #13 = Asciz setBar; const #14 = Asciz (Ljava/lang/String;)V; const #15 = Asciz SourceFile; const #16 = Asciz Foo.java; const #17 = NameAndType #7:#8;// "":()V const #18 = NameAndType #5:#6;// bar:Ljava/lang/String; const #19 = Asciz Foo; const #20 = Asciz java/lang/Object; { public Foo(); Signature: ()V Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0 public java.lang.String getBar(); Signature: ()Ljava/lang/String; Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn LineNumberTable: line 5: 0 public void setBar(java.lang.String); Signature: (Ljava/lang/String;)V Code: Stack=2, Locals=2, Args_size=2 0: aload_0 1: aload_1 2: putfield #2; //Field bar:Ljava/lang/String; 5: return LineNumberTable: line 8: 0 line 9: 5 }
      
      





अब हम देखते हैं कि वे किस तरह के अजीब ऑपरेंड हैं। उदाहरण के लिए, # 2:



const # 2 = फ़ील्ड # 3. # 18; // Foo.bar:Ljava/lang/String;



यह संदर्भित है:



const # 3 = वर्ग # 19; // फू

const # 18 = NameAndType # 5: # 6; // बार: Ljava / lang / स्ट्रिंग;



और इसी तरह।



ध्यान दें कि, प्रत्येक ऑपरेशन कोड एक नंबर (0: aload_0) के साथ लेबल किया गया है। यह फ्रेम के अंदर निर्देश की स्थिति का एक संकेत है - मैं आगे बताऊंगा कि इसका क्या मतलब है।



यह समझने के लिए कि बाइटकोड कैसे काम करता है, बस निष्पादन मॉडल को देखें। JVM स्टैक-आधारित निष्पादन मॉडल का उपयोग करता है। प्रत्येक थ्रेड में एक JVM स्टैक होता है जिसमें फ़्रेम होते हैं। उदाहरण के लिए, यदि हम डिबगर में एप्लिकेशन चलाते हैं, तो हम निम्नलिखित फ़्रेम देखेंगे:

छवि



हर बार जब विधि को बुलाया जाता है, तो एक नया फ्रेम बनाया जाता है। फ़्रेम में ऑपरेंड स्टैक, स्थानीय चर की एक सरणी और निष्पादन विधि के वर्ग के स्थिरांक के पूल का लिंक होता है।

छवि



स्थानीय चर की सरणी का आकार स्थानीय चर और विधि मापदंडों की संख्या और आकार के आधार पर, संकलन समय पर निर्धारित किया जाता है। ऑपरेंड्स का ढेर - स्टैक में मान लिखने और हटाने के लिए LIFO स्टैक; आकार भी संकलन समय पर निर्धारित किया जाता है। कुछ ऑपकोड स्टैक में मान जोड़ते हैं, अन्य स्टैक से ऑपरेंड लेते हैं, अपनी स्थिति बदलते हैं और स्टैक में वापस आते हैं। ऑपरेंड स्टैक का उपयोग (रिटर्न मान) विधि द्वारा लौटाए गए मानों को प्राप्त करने के लिए भी किया जाता है।

 public String getBar(){ return bar; } public java.lang.String getBar(); Code: 0: aload_0 1: getfield #2; //Field bar:Ljava/lang/String; 4: areturn
      
      





इस विधि के लिए बाइटकोड में तीन ऑपकोड होते हैं। पहले opcode, aload_0, स्टैक पर स्थानीय चर तालिका से इंडेक्स 0 के साथ मान को धक्का देता है। कंस्ट्रक्टरों और उदाहरण के तरीकों के लिए स्थानीय चर की तालिका में इस संदर्भ में हमेशा 0. का एक सूचकांक होता है, अगला ऑफीकोड, गेटफील्ड, ऑब्जेक्ट फ़ील्ड प्राप्त करता है। अंतिम विवरण, पुनरावृत्ति, एक विधि से एक संदर्भ देता है।



प्रत्येक विधि में एक समान बायोटेक सरणी है। हेक्स संपादक में .class फ़ाइल की सामग्री को देखते हुए, आप बायटेकोड सरणी में निम्नलिखित मान देखेंगे:



छवि



तो, getBar विधि के लिए बाईटेकोड 2A B4 00 02 B0 है। 2A अलोएड_0 को संदर्भित करता है, बी 0 आरटर्न को संदर्भित करता है। यह अजीब लग सकता है कि विधि के लिए बाइटकोड में तीन निर्देश हैं, और बाइट सरणी में 5 तत्व हैं। यह इस तथ्य के कारण है कि getfield (B4) को दो मापदंडों (00 02) की आवश्यकता है, सरणी में 2 और 3 पर कब्जा कर रहा है, इसलिए सरणी में 5 तत्व हैं। आरटर्न निर्देश को 4 पदों पर स्थानांतरित किया गया है।

स्थानीय चर तालिका



यह समझने के लिए कि स्थानीय चर के साथ क्या होता है, हम एक और उदाहरण का उपयोग करते हैं:

 public class Example { public int plus(int a){ int b = 1; return a + b; } }
      
      





यहां दो स्थानीय चर हैं - विधि पैरामीटर और स्थानीय चर int b। यहां देखें कि बायकटोड कैसा दिखता है:

 public int plus(int); Code: Stack=2, Locals=3, Args_size=2 0: iconst_1 1: istore_2 2: iload_1 3: iload_2 4: iadd 5: ireturn LineNumberTable: line 5: 0 line 6: 2
      
      





LocalVariableTable:

लंबाई स्लॉट नाम हस्ताक्षर प्रारंभ करें

0 6 0 यह LExample;

0 6 1 ए I

2 4 2 बी आई



विधि iconst_1 के साथ निरंतर 1 लोड करती है और इसे istore_2 के साथ स्थानीय चर 2 में रखती है। अब, स्थानीय चर तालिका में, स्लॉट 2 को चर b द्वारा कब्जा कर लिया गया है, जैसा कि अपेक्षित था। अगला, iload_1 स्टैक पर मान लोड करता है, iload_2 बी के मूल्य को लोड करता है। iadd स्टैक से 2 ऑपरेंड करता है, उन्हें जोड़ता है, और विधि का मान लौटाता है।

अपवाद हैंडलिंग



अपवाद हैंडलिंग के मामले में बाइट कोड कैसे प्राप्त किया जाता है, इसका एक दिलचस्प उदाहरण, उदाहरण के लिए, ट्राइ-कैच-आखिर निर्माण के लिए।

 public class ExceptionExample { public void foo(){ try { tryMethod(); } catch (Exception e) { catchMethod(); }finally{ finallyMethod(); } } private void tryMethod() throws Exception{} private void catchMethod() {} private void finallyMethod(){} }
      
      





फू के लिए बाइटकोड () विधि:

 public void foo(); Code: 0: aload_0 1: invokespecial #2; //Method tryMethod:()V 4: aload_0 5: invokespecial #3; //Method finallyMethod:()V 8: goto 30 11: astore_1 12: aload_0 13: invokespecial #5; //Method catchMethod:()V 16: aload_0 17: invokespecial #3; //Method finallyMethod:()V 20: goto 30 23: astore_2 24: aload_0 25: invokespecial #3; //Method finallyMethod:()V 28: aload_2 29: athrow 30: return Exception table: from to target type 0 4 11 Class java/lang/Exception 0 4 23 any 11 16 23 any 23 24 23 any
      
      





कंपाइलर कोशिश-कैच-इन के अंदर संभव सभी लिपियों के लिए कोड उत्पन्न करता है: आखिरकार मैमथोड () ब्लॉक तीन बार (!) है। कोशिश ब्लॉक को संकलित किया गया जैसे कि कोई प्रयास नहीं था और इसे अंत में विलय कर दिया गया था:

0: aload_0

1: invokespecial # 2; // विधि tryMethod :() वी

4: aload_0

5: invokespecial # 3; // विधि अंत में मैथोड :() वी

यदि ब्लॉक निष्पादित किया जाता है, तो गोटो निर्देश रिटर्न ओपोड के साथ निष्पादन को 30 वें स्थान पर फेंक देता है।



यदि TryMethod एक अपवाद फेंकता है, तो अपवाद तालिका से पहला उपयुक्त (आंतरिक) अपवाद हैंडलर चुना जाएगा। अपवादों की तालिका से हम देखते हैं कि अपवाद पकड़ने की स्थिति 11 है:



0 4 11 कक्षा जावा / लैंग / अपवाद



यह निष्पादन को फेंक देता हैमैथोड () और अंत में मैथोड ():



11: astore_1

12: aload_0

13: अदृश्य # 5; // catchMethod विधि :() वी

16: aload_0

17: अदृश्य # 3; // अंत में मैथोड विधि :() वी



यदि निष्पादन के दौरान एक और अपवाद दिया जाता है, तो हम देखेंगे कि अपवाद तालिका में स्थिति 23 होगी:



0 4 23 किसी भी

११ १६ २३ कोई

२३ २४ २३ कोई



23 से शुरू होने वाले निर्देश:



23: astore_2

24: aload_0

25: अदृश्य # 3; // विधि अंत में मैथोड :() वी

28: aload_2

29: एथ्रो

30: वापसी



तो आखिरकार मेथोड () को वैसे भी अंजाम दिया जाएगा, जिसमें aload_2 और एथ्रो अनहेल्डेड अपवाद को फेंकते हैं।



निष्कर्ष



ये JVM बायटेकोड क्षेत्र से कुछ ही बिंदु हैं। अधिकांश डेवलपरवॉर्क पीटर हैगर के लेख, जावा बाइटकोड से थे: अंडरटेकिंग को समझना आपको एक बेहतर प्रोग्रामर बनाता है। लेख थोड़ा पुराना है, लेकिन अभी भी प्रासंगिक है। बीसीईएल उपयोगकर्ता गाइड में बाइटकोड की मूल बातों का एक सभ्य विवरण शामिल है, इसलिए मैं इसे दिलचस्पी रखने वालों को पढ़ने का सुझाव दूंगा। इसके अलावा, एक वर्चुअल मशीन का विनिर्देश सूचना का एक उपयोगी स्रोत भी हो सकता है, लेकिन इसे पढ़ना आसान नहीं है, इसके अलावा कोई ग्राफिक सामग्री नहीं है जिसे समझना उपयोगी हो सकता है।



सामान्य तौर पर, मुझे लगता है कि यह समझना कि कैसे बाइटकोड काम करता है, जावा प्रोग्रामिंग के अपने ज्ञान को गहरा बनाने में एक महत्वपूर्ण बिंदु है, खासकर उन लोगों के लिए जो फ्रेमवर्क, जेवीएम भाषा संकलक, या अन्य उपयोगिताओं को देख रहे हैं।




All Articles