使用Java開發(fā),我們不必關(guān)系編譯器對源碼做了什么,但是我們現(xiàn)在要探索java許多的幕后細(xì)節(jié)。
二進(jìn)制類的格式實(shí)際上是由jvm規(guī)范定義的,他們是編譯器從java源碼生成,并存儲(chǔ)在擴(kuò)展名為.class文件中,不重要的部分不是源碼以及如何存儲(chǔ)源碼,而是格式本身,一個(gè)類的格式可能是下面這樣:
public class HelloWorld{
public static void main(){
System.out.println("helloworld");
}
}
Class文件結(jié)構(gòu),通過tag+長度的形式來表示某種結(jié)構(gòu),最基本的組成是8位的無符號(hào)數(shù),分別是U1,U2,U4,U8,這些字節(jié)可以被解釋成有符號(hào)數(shù),UTF-8編碼的字符等。
參考我的:java中的有符號(hào),無符號(hào)操作以及DataInputStream
數(shù)據(jù)底層讀取都是一個(gè)byte,那作為int的輸入,實(shí)際是0X000000byte,所以肯定是無符號(hào)類型,如果是想讀取有符號(hào)的,比如byte,或者short,那可以直接做轉(zhuǎn)換,因?yàn)?/span>java在寬類型轉(zhuǎn)為窄類型的時(shí)候,直接截取掉長的byte。下面給出一個(gè)讀取short型以及讀取int型的例子
int
(((a & 0xff) << 24) | ((b & 0xff) << 16) |
((c & 0xff) << 8) | (d & 0xff))
或者是
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
Short
int ch1 = in.read();
int ch2 = in.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (short)((ch1 << 8) + (ch2 << 0));
UTF-8變體結(jié)構(gòu)的規(guī)則如下:
1. 標(biāo)準(zhǔn)的ascii碼表只需要7位(不包括擴(kuò)展表),當(dāng)然還有一個(gè)NULL,在這里,除了Null以外的ASCII碼全部使用一個(gè)字節(jié)表示
All characters in the range '\u0001' to '\u007F' are represented by a single byte:
2.NULL字符(0000)和在00800007FF范圍內(nèi)的使用雙字節(jié)表示:
The bytes represent the character with the value ((x & 0x1f) << 6) + (y & 0x3f).
3.從0800到FFFF的所有字符使用三個(gè)字節(jié)表示:
他和標(biāo)準(zhǔn)UTF8的區(qū)別在于:
1.標(biāo)準(zhǔn)UTF-8編碼,空字符null使用一個(gè)字節(jié)表示,在CONSTANT_Utf8_info表中,空字符,使用兩個(gè)字節(jié)表示,這樣字節(jié)數(shù)組就不會(huì)為0,(據(jù)說是為了排除編碼\0帶來的干擾)
2.Bytes項(xiàng)中只使用了標(biāo)準(zhǔn)UTF-8編碼中的單字節(jié)、雙字節(jié)和三字節(jié),而標(biāo)準(zhǔn)UTF-8編碼還包括未在CONSTANT_Utf8_info表中使用的較長的格式。
可以參考標(biāo)準(zhǔn)UTF-8編碼:UTF-8
對Java class文件的精確定義使得所有Java虛擬機(jī)都能夠正確地讀取和解釋所有Java class文件。
{
0xCAFEBABE,小版本號(hào),大版本號(hào),常量池大小,常量池?cái)?shù)組,
訪問控制標(biāo)記,當(dāng)前類信息,父類信息,實(shí)現(xiàn)的接口個(gè)數(shù),實(shí)現(xiàn)的接口信息數(shù)組,
域個(gè)數(shù),域信息數(shù)組,方法個(gè)數(shù),方法信息數(shù)組,屬性個(gè)數(shù),屬性信息數(shù)組
}
二進(jìn)制類表示中首先是“cafe babe”特征符,它標(biāo)識(shí) Java 二進(jìn)制類格式,這個(gè)特征符恰好是一種驗(yàn)證一個(gè)數(shù)據(jù)塊 確實(shí)聲明成 Java 類格式的一個(gè)實(shí)例的簡單方法。任何 Java 二進(jìn)制類(甚至是文件系統(tǒng)中沒有出現(xiàn)的類)都需要以這四個(gè)字節(jié)作為開始。 (think in java中,說作者正在思念一個(gè)女人)。
參考我的:class文件格式詳細(xì)
這里可以學(xué)習(xí)到得主要就是怎么設(shè)計(jì)一個(gè)緊湊的格式,里面有個(gè)讓我映像深刻的主要是:
1.UTF_8結(jié)構(gòu)的大量使用,幾乎所有其他結(jié)構(gòu)都指向了這種結(jié)構(gòu),比如字段名,字段類型,方法名,方法參數(shù)類型,類名等等。
2.類構(gòu)造器方法<clint>,以及實(shí)例構(gòu)造器方法<init>
3.指令描述Code屬性,方法的指令不是在方法體中的,而是引用了一個(gè)Code屬性
4.局部變量表,LocalVariableTable以及LineNumberTable(字節(jié)碼和sourcecode行數(shù)的對應(yīng)關(guān)系),用于提供調(diào)試時(shí)候的信息,包括變量的作用域范圍等
Java字節(jié)碼有259個(gè)操作符(類似Ascii),用一個(gè)byte(8位)就可以表示了。
0x00 - 0xca, 0xfe, and 0xff 被指定為不同的值. 0xba 因?yàn)橐恍v史原因沒有被使用。 0xca 被作為一個(gè)debug的斷點(diǎn),在程序中沒有被使用. 同樣, 0xfe 和0xff 也沒有被語言使用,而是被保留為jvm內(nèi)部使用。
指令可以被寬泛的分為幾個(gè)組:
1.Load和Store(例如aload_0和istore)
2.運(yùn)算和邏輯(例如ladd,fcmpl)
3.類型轉(zhuǎn)換(例如i2b,d2i)
4.對象的創(chuàng)建和操作(例如new putfiled)
5.操作數(shù)棧管理(例如swap、Dup2)
6.流程控制(例如ifeq、goto)
7方法的執(zhí)行和返回(例如invokespecial、areturn)
另外,還有幾個(gè)特殊的指令,比如異常、同步等。
很多指令的前綴和/或后綴表明了他們操作的類型:
例如,iadd將會(huì)將兩個(gè)integer數(shù)相加,dadd將兩個(gè)double類型的數(shù)相加?!?/span>const”、“load”、“store”指令帶一個(gè)后綴“_n”,對于load和store來說,n是一個(gè)1-3的數(shù);對于const,對大的類型要根據(jù)類型而定。
“const”指令將一個(gè)指定類型的數(shù)壓入棧頂,例如,“iconst_5”將一個(gè)integer型的5壓入棧,“dconst_1”將一個(gè)double型的1入棧。還有一個(gè)特殊的iconst_null,會(huì)將null入棧。
對于load和store來說,n表示變量表中的位置來載入或者存入。“aload_0”指令將變量[0]壓入棧頂(通常是“this”)。“istore_1”將棧頂?shù)臄?shù)存入變量[1];對于更大的n,就不能使用后綴,而需要使用操作數(shù)。
Java字節(jié)碼是“面向棧的編程語言”,類似的有X86的處理器,比如:
add eax, edx
mov ecx, eax
上面的指令將兩個(gè)數(shù)相加,并且將結(jié)果移到另一個(gè)地方,相似的字節(jié)碼類似下面:
0 iload_1
1 iload_2
2 iadd
3 istore_3
上面的代碼中,通過iadd指令,先將兩個(gè)數(shù)壓入棧,并且相加,將結(jié)果重新壓入棧。Istrore指令將棧頂?shù)闹狄迫刖植孔兞繀^(qū)。每個(gè)指令前面的數(shù),僅僅代表每個(gè)指令相對于方法開始的偏移量。一個(gè)“getName”方法,類似下面這樣:
Method java.lang.String getName()
0 aload_0 // The "this" object is stored in location 0 of the variable table
1 getfield #5 <Field java.lang.String name>
// This instructon pops an object from the top of the stack, retrieves the specified
// field from it, and pushes the field onto the stack.
// In this example, the "name" field is the fifth field of the class.
4 areturn // Returns the object on top of the stack from the method.
下面是另一個(gè)例子:
outer
for (int i = 2; i < 1000; i++) {
for (int j = 2; j < i; j++) {
if (i % j == 0)
continue outer;
}
System.out.println (i);
}
1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge 44
9: iconst_2
10: istore_2
11: iload_2
12: iload_1
13: if_icmpge 31
16: iload_1
17: iload_2
18: irem
19: ifne 25
22: goto 38
25: iinc 2, 1
28: goto 11
31: getstatic #84; //Field java/lang/System.out:Ljava/io/PrintStream;
34: iload_1
35: invokevirtual #85; //Method java/io/PrintStream.println:(I)V
38: iinc 1, 1
41: goto 2
44: return
1.本地下載的指令集合:“Instructions2.doc.html”,以及java虛擬機(jī)的書。
2.wiki http://en.wikipedia.org/wiki/Java_bytecode
3.http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings 簡單指令集合
Stack-oriented programming language
http://en.wikipedia.org/wiki/Stack-oriented_programming_language
聯(lián)系客服