講完了class文件中的常量池,我們就相當於克服了class文件中最麻煩的模塊了。現在,我們來看一下class文件中緊接着常量池後面的幾個東西:訪問標誌、類索引、父類索引、接口索引集合。
1. 訪問標誌、類索引、父類索引、接口索引集合 在class文件中的位置
好,讓我們來一一擊破它們,看看它們到底是什麼東西。
2. 訪問標誌(access_flags)能夠表示什麼?
訪問標誌(access_flags)緊接着常量池後,佔有兩個字節,總共16位,如下圖所示:
當JVM在編譯某個類或者接口的源代碼時,JVM會解析出這個類或者接口的訪問標誌信息,然後,將這些標誌設置到訪問標誌(access_flags)這16個位上。JVM會考慮如下設置如下訪問表示信息:
a. 我們知道,每個定義的類或者接口都會生成class文件(這裏也包括內部類,在某個類中定義的靜態內部類也會單獨生成一個class文件)。
對於定義的類,JVM在將其編譯成class文件時,會將class文件的訪問標誌的第11位設置爲1 。第11位叫做ACC_SUPER標誌位;
對於定義的接口,JVM在將其編譯成class文件時,會將class文件的訪問標誌的第8位 設置爲 1 。第8位叫做ACC_INTERFACE標誌位;
b. class文件表示的類或者接口的訪問權限有public類型的和包package類型的。
如果類或者接口被聲明爲public類型的,那麼,JVM將其編譯成class文件時,會將class文件的訪問標誌的第16位設置爲1 。第16位叫做ACC_PUBLIC標誌符;
c. 類是否爲抽象類型的,即我們定義的類有沒有被abstract關鍵字修飾,即我們定義的類是否爲抽象類。
如果我們形如:
[java] view plain copy print?
定義某個類時,JVM將它編譯成class文件的時候,會將class文件的訪問標誌的第7位設置爲1 。第7位叫做ACC_ABSTRACT標誌位。 另外值得注意的是,對於定義的接口,JVM在編譯接口的時候也會對class文件的訪問標誌上的ACC_ABSTRACT標誌位設置爲 1;
public abstract class MyClass{......}
d. 該類是否被聲明瞭final類型,即表示該類不能被繼承。
此時JVM會在編譯class文件的過程中,會將class文件的訪問標誌的第12位設置爲 1 。第12位叫做ACC_FINAL標誌位;
e.如果我們這個class文件不是JVM通過Java源代碼文件編譯而成的,而是用戶自己通過class文件的組織規則生成的,那麼,一般會對class文件的訪問標誌第4位設置爲 1 。通過JVM編譯源代碼產生的class文件此標誌位爲 0,第4位叫做ACC_SYNTHETIC標誌位;
f. 枚舉類,對於定義的枚舉類如:public enum EnumTest{....},JVM也會對此枚舉類編譯成class文件,這時,對於這樣的class文件,JVM會對訪問標誌第2位設置爲 1 ,以表示它是枚舉類。第2位叫做ACC_ENUM標誌位;
g. 註解類,對於定義的註解類如:public @interface{.....},JVM會對此註解類編譯成class文件,對於這樣的class文件,JVM會將訪問標誌第3位設置爲1,以表示這是個註解類,第3位叫做ACC_ANNOTATION標誌位。
當JVM確定了上述標誌位的值後,就可以確定訪問標誌(access_flags)的值了。實際上JVM上述標誌會根據上述確定的標誌位的值,對這些標誌位的值取或,便得到了訪問標誌(access_flags)。如下圖所示:
舉例:定義一個最簡單的類Simple.java,使用編譯器編譯成class文件,然後觀察class文件中的訪問標誌的值,以及使用javap -v Simple 查看訪問標誌。
[java] view plain copy print?
使用UltraEdit查看編譯成的class文件,如下圖所示:
package com.louis.jvm;
public class Simple {
}
上述的圖中×××部分表示的是常量池部分,具體爲什麼是常量池部分不是本文的重點,有興趣的讀者可以參考我的《Java虛擬機原理圖解》系列關於常量池的博客,你就可以很輕鬆地識別常量它們了。
常量池後面緊跟着就是訪問標誌,它的十六進制值爲0x0021,二進制的值爲:00000000 00100001,由二進制的1的位數可以得出第11、16位爲1,分別對應ACC_SUPER標誌位和ACC_PUBLIC標誌位。
也可以通過一下運算:
0x0021 = 0x0001 | 0x0020, 即: 訪問標誌表示的標誌是ACC_PUBLIC + ACC_SUPER
爲了驗證我們的運算,使用javap -v Simple查看反編譯信息如下:(小技巧:使用javap -v Simple指令的結果展示在命令提示符下顯示不友好,一般我是使用javap -v Simple > temp.txt,將結果重定向到文件中,然後查看文件)
3. 類索引(this_class)是什麼?
我們知道一般情況下一個Java類源文件經過JVM編譯會生成一個class文件,也有可能一個Java類源文件中定義了其他類或者內部類,這樣編譯出來的class文件就不止一個,但每一個class文件表示某一個類,至於這個class表示哪一個類,便可以通過 類索引 這個數據項來確定。JVM通過類的完全限定名確定是某一個類。
類索引的作用,就是爲了指出class文件所描述的這個類叫什麼名字。
類索引緊接着訪問標誌的後面,佔有兩個字節,在這兩個字節中存儲的值是一個指向常量池的一個索引,該索引指向的是CONSTANT_Class_info常量池項,
以上面定義的Simple.class 爲例,如下圖所示,查看他的類索引在什麼位置和取什麼值。
由上可知,它的類索引值爲0x0001,那麼,它指向了常量池中的第一個常量池項,那我們再看一下常量池中的信息。使用javap -v Simple,常量池中有以下信息:
可以看到常量池中的第一項是CONSTANT_Class_info項,它表示一個"com/louis/jvm/Simple"的類名。即類索引是告訴我們這個class文件所表示的是哪一個類。
3. 父類索引(super_class)是什麼?
Java支持單繼承模式,除了java.lang.Object 類除外,每一個類都會有且只有一個父類。class文件中緊接着類索引(this_class)之後的兩個字節區域表示父類索引,跟類索引一樣,父類索引這兩個字節中的值指向了常量池中的某個常量池項CONSTANT_Class_info,表示該class表示的類是繼承自哪一個類。
4. 接口索引集合(interfaces)是什麼?
一個類可以不實現任何接口,也可以實現很多個接口,爲了表示當前類實現的接口信息,class文件使用瞭如下結構體描述某個類的接口實現信息:
由於類實現的接口數目不確定,所以接口索引集合的描述的前部分叫做接口計數器(interfaces_count),接口計數器佔用兩個字節,其中的值表示着這個類實現了多少個接口,緊跟着接口計數器的部分就是接口索引部分了,每一個接口索引佔有兩個字節,接口計數器的值代表着後面跟着的接口索引的個數。接口索引和類索引和父類索引一樣,其內的值存儲的是指向了常量池中的常量池項的索引,表示着這個接口的完全限定名。
舉例:
定義一個Worker接口,然後類Programmer實現這個Worker接口,然後我們觀察Programmer的接口索引集合是怎樣表示的。
[java] view plain copy print?
/**
* Worker 接口類
* @author luan louis
*/
public interface Worker{
public void work();
}
[java] view plain copy print?
package com.louis.jvm;
public class Programmer implements Worker {
@Override
public void work() {
System.out.println("I'm Programmer,Just coding....");
}
}