(<深入分析Java Web技術(shù)內(nèi)幕>筆記)
java語言在宣傳時(shí)打出的名號就是"一次編譯,到處運(yùn)行", 也就是支持跨平臺運(yùn)行,其實(shí)這是java文件編譯成class文件來實(shí)現(xiàn)的,那么class文件是怎么生成的?clss文件又有哪些內(nèi)容?本篇筆記就是記錄相關(guān)的學(xué)習(xí)內(nèi)容的.
JVM指令集
先來一段幾乎每個(gè)人入門一門語言都會寫的代碼:
public class ClassLearn {
public static void main(String[] args) {
System.out.println("hello world!");
}
}
就是一個(gè)類有一個(gè)main方法,方法內(nèi)部打印了"hello world!",我們先將其編譯為class文件,再運(yùn)行:
// oolong是一種匯編語言,可以編譯class文件為.j
java COM.sootNsmoke.oolong.Gnoloo ClassLearn.class
會生成一個(gè)ClassLearn.j文件,查看一下文件內(nèi)容:
以"."符號開頭表示是一個(gè)基本的屬性項(xiàng),就是一個(gè)java的基本概念(一個(gè)類,方法,屬性,對象或者一個(gè)接口)
與類相關(guān)的指令
-
.source ClassLearn.java
表示代碼的源文件是ClassLearn.java -
.class public super ClassLearn
表示這是一個(gè)類且公有的類名是ClassLearn -
.super java/lang/Object
表示這個(gè)類的父類是Object
與類相關(guān)的指令:
指令 | 參數(shù) | 解釋 |
---|---|---|
checkcast | class | 檢驗(yàn)類型轉(zhuǎn)換(ClassCastException) |
getfield | class/field desc | 獲取類的實(shí)例域,并將值壓入棧頂 |
getstatic | class/field desc | 獲取類的靜態(tài)域,并將值壓入棧頂 |
instanceof | class | 檢查對象是否是類的實(shí)例,是1壓入棧頂,否0壓入棧頂 |
new | class | 創(chuàng)建一個(gè)對象,將其引入值壓入棧頂 |
方法定義
-
.method public <init> ()V
表示是一個(gè)公有方法,沒有參數(shù),返回值是void
,<init>
表示是構(gòu)造方法 -
.method public static main([Ljava/lang/String;)V
與上一條類似,[
表示數(shù)組,L
表示是一個(gè)類形式而不是基本數(shù)據(jù)類型(int,char等),凡是L
最后都以;
結(jié)尾,表示類結(jié)束
與方法相關(guān)的JVM指令:
指令 | 操作數(shù) | 解釋 |
---|---|---|
invokeinterface | class/method desc n | 調(diào)用接口方法 |
invokespecial | class/method desc | 調(diào)用超類構(gòu)造方法,實(shí)例初始化方法,私有方法 |
invokestatic | class/method desc | 調(diào)用靜態(tài)方法 |
invokevirtual | class/method desc | 調(diào)用實(shí)例方法 |
類的屬性的定義
數(shù)據(jù)類型:
數(shù)據(jù)類型 | 表示方式 |
---|---|
數(shù)組 | [ |
類 | L; |
byte | B |
boolean | Z |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
void | V |
修飾屬性:
修飾符 | 說明 |
---|---|
public | 公有 |
private | 私有 |
protected | 子類和包可見 |
static | 靜態(tài) |
final | 不可修改 |
volatile | 弱引用 |
transient | 臨時(shí)屬性 |
class文件頭的表示形式
看一看之前的代碼字節(jié)碼形式是怎么樣的:
java COM.sootNsmoke.oolong.DumpClass ClassLearn.class
輸出:
000000 cafebabe magic = ca fe ba be
000004 0000 minor version = 0
000006 0034 major version = 52
000008 001d 29 constants
00000a 0a0006000f 1. Methodref class #6 name-and-type #15
00000f 0900100011 2. Fieldref class #16 name-and-type #17
000014 080012 3. String #18
000017 0a00130014 4. Methodref class #19 name-and-type #20
00001c 070015 5. Class name #21
00001f 070016 6. Class name #22
000022 010006 7. UTF length=6
000025 3c696e69743e <init>
00002b 010003 8. UTF length=3
00002e 282956 ()V
000031 010004 9. UTF length=4
000034 436f6465 Code
000038 01000f 10. UTF length=15
00003b 4c696e654e756d6265725461626c65 LineNumberTable
00004a 010004 11. UTF length=4
00004d 6d61696e main
000051 010016 12. UTF length=22
000054 285b4c6a6176612f6c616e672f537472 ([Ljava/lang/Str
000064 696e673b2956 ing;)V
00006a 01000a 13. UTF length=10
00006d 536f7572636546696c65 SourceFile
000077 01000f 14. UTF length=15
00007a 436c6173734c6561726e2e6a617661 ClassLearn.java
000089 0c00070008 15. NameAndType name #7 descriptor #8
00008e 070017 16. Class name #23
000091 0c00180019 17. NameAndType name #24 descriptor #25
000096 01000c 18. UTF length=12
000099 68656c6c6f20776f726c6421 hello world!
0000a5 07001a 19. Class name #26
0000a8 0c001b001c 20. NameAndType name #27 descriptor #28
0000ad 01000a 21. UTF length=10
0000b0 436c6173734c6561726e ClassLearn
0000ba 010010 22. UTF length=16
0000bd 6a6176612f6c616e672f4f626a656374 java/lang/Object
0000cd 010010 23. UTF length=16
0000d0 6a6176612f6c616e672f53797374656d java/lang/System
0000e0 010003 24. UTF length=3
0000e3 6f7574 out
0000e6 010015 25. UTF length=21
0000e9 4c6a6176612f696f2f5072696e745374 Ljava/io/PrintSt
0000f9 7265616d3b ream;
0000fe 010013 26. UTF length=19
000101 6a6176612f696f2f5072696e74537472 java/io/PrintStr
000111 65616d eam
000114 010007 27. UTF length=7
000117 7072696e746c6e println
00011e 010015 28. UTF length=21
000121 284c6a6176612f6c616e672f53747269 (Ljava/lang/Stri
000131 6e673b2956 ng;)V
000136 0021 access_flags = 33
000138 0005 this = #5
00013a 0006 super = #6
00013c 0000 0 interfaces
00013e 0000 0 fields
000140 0002 2 methods
Method 0:
000142 0001 access flags = 1
000144 0007 name = #7<<init>>
000146 0008 descriptor = #8<()V>
000148 0001 1 field/method attributes:
field/method attribute 0
00014a 0009 name = #9<Code>
00014c 0000001d length = 29
000150 0001 max stack: 1
000152 0001 max locals: 1
000154 00000005 code length: 5
000158 2a 0 aload_0
000159 b70001 1 invokespecial #1
00015c b1 4 return
00015d 0000 0 exception table entries:
00015f 0001 1 code attributes:
code attribute 0:
000161 000a name = #10<LineNumberTable>
000163 00000006 length = 6
Line number table:
000167 0001 length = 1
000169 00000008 start pc: 0 line number: 8
Method 1:
00016d 0009 access flags = 9
00016f 000b name = #11<main>
000171 000c descriptor = #12<([Ljava/lang/String;)V>
000173 0001 1 field/method attributes:
field/method attribute 0
000175 0009 name = #9<Code>
000177 00000025 length = 37
00017b 0002 max stack: 2
00017d 0001 max locals: 1
00017f 00000009 code length: 9
000183 b20002 0 getstatic #2
000186 1203 3 ldc #3
000188 b60004 5 invokevirtual #4
00018b b1 8 return
00018c 0000 0 exception table entries:
00018e 0001 1 code attributes:
code attribute 0:
000190 000a name = #10<LineNumberTable>
000192 0000000a length = 10
Line number table:
000196 0002 length = 2
000198 0000000a start pc: 0 line number: 10
00019c 0008000b start pc: 8 line number: 11
0001a0 0001 1 classfile attributes
Attribute 0:
0001a2 000d name = #13<SourceFile>
0001a4 00000002 length = 2
0001a8 000e sourcefile index = #14
Done.
看起來特別長,但是真正需要理解的并不是很多,先看頭部信息:
000000 cafebabe magic = ca fe ba be
000004 0000 minor version = 0
000006 0034 major version = 52
第一行是一個(gè)標(biāo)識符,表示是一個(gè)標(biāo)準(zhǔn)的class文件,由一個(gè)32位無符號整數(shù)表示,cafebabe
是其16進(jìn)制表示形式,如果不是這個(gè),JVM就不認(rèn)為這是一個(gè)class文件,也就不會加載,后面兩個(gè)字節(jié)表示說最大版本和最小版本的范圍,從最初的Java到Java8是45.3~53.0,JVM在加載時(shí)也會檢查這個(gè)是否符合條件
常量池
根據(jù)輸出顯示,共有29個(gè)常量,但是看顯示只有1~28,是因?yàn)?是保留常量,每個(gè)常量都由3個(gè)字節(jié)描述:
第一個(gè)表示這個(gè)常量是什么類型的,JVM中定義了12種類型常量,每個(gè)都會對應(yīng)一個(gè)數(shù)值
接下來分別說明幾個(gè)較重要的
UTF8常量
字符編碼格式,存儲多個(gè)字節(jié)長度的字符串值(類名,方法名等)
Fieldref,Methodref常量
描述Class中的屬性項(xiàng)和方法的
第一個(gè)字節(jié)09代表是Fieldref類型,10則代表Methodref類型;后面兩個(gè)字節(jié)代表屬于哪個(gè)類;最后兩個(gè)字表示常量的name和type
Class常量
表示該類的名稱,會指向另一個(gè)UTF8類型的常量(之前提到了UTF8就是表示類名和方法名等的字符串)
利用之前的輸出
00008e 070017 16. Class name #23
第一個(gè)字節(jié)07就代表該常量是Class類型,后面兩個(gè)字節(jié)0017指向第23個(gè)常量,而第23個(gè)常量是:
0000cd 010010 23. UTF length=16
0000d0 6a6176612f6c616e672f53797374656d java/lang/System
就是一個(gè)UTF8常量,存儲的是java/lang/System
,也就是類名
NameAndType常量
為了表示Methodref
和Fieldref
的名稱和類型描述做進(jìn)一步說明存在的,名稱也是通常由UTF8
常量描述,類型也由UTF8
描述
0007指向第7個(gè)常量,表示
Methodref
或Fieldref
的名稱,而0008則表示Methodref
的返回類型或Fieldref
參數(shù)類型
類信息
常量列表后面就是類本身的信息描述了,如訪問控制,名稱,類型,是否有父類,是否實(shí)現(xiàn)了接口等
000136 0021 access_flags = 33
000138 0005 this = #5
00013a 0006 super = #6
00013c 0000 0 interfaces
兩個(gè)字節(jié)只使用了5bit,
ACC_PUBLIC
代表類是否是public類(是為1,否為0),ACC_FINAL
代表是否是final類,ACC_SUPER
代表是否存在父類(除了Obejct其他類都有父類),ACC_INTERFACE
代表是否是接口類,ACC_ABSTRACT
代表是否是抽象類;后面兩個(gè)字節(jié)0005代表該類名稱,指向第5個(gè)常量;后面的0006代表父類的名稱,指向第6個(gè)常量;0000代表沒有實(shí)現(xiàn)接口類
Fields和Methods定義
00013e 0000 0 fields
000140 0002 2 methods
Method 0:
000142 0001 access flags = 1
000144 0007 name = #7<<init>>
000146 0008 descriptor = #8<()V>
000148 0001 1 field/method attributes:
field/method attribute 0
00014a 0009 name = #9<Code>
00014c 0000001d length = 29
000150 0001 max stack: 1
000152 0001 max locals: 1
000154 00000005 code length: 5
000158 2a 0 aload_0
000159 b70001 1 invokespecial #1
00015c b1 4 return
00015d 0000 0 exception table entries:
00015f 0001 1 code attributes:
code attribute 0:
000161 000a name = #10<LineNumberTable>
000163 00000006 length = 6
Line number table:
000167 0001 length = 1
000169 00000008 start pc: 0 line number: 8
Method1與Method0類似,就不粘過來了
前4個(gè)字節(jié)表示該類有幾個(gè)屬性,幾個(gè)方法,后面就是每個(gè)屬性和類的具體定義了
方法當(dāng)然也有訪問控制,也由兩個(gè)字節(jié)定義:
與類描述基本一致,就不列舉說明了
接下來4個(gè)字節(jié):
000144 0007 name = #7<<init>>
000146 0008 descriptor = #8<()V>
定義了方法的名稱和類型描述(NameAndType)
接下來是具體的代碼實(shí)現(xiàn)的定義了,包括代碼長度(最長64K字節(jié)長度)
接下來:
000150 0001 max stack: 1
000152 0001 max locals: 1
定義了該方法使用的棧的深度及本地常量的最大個(gè)數(shù),會在JVM類加載做檢查,超過這兩個(gè)值,會拒絕加載
接下來:
000154 00000005 code length: 5
000158 2a 0 aload_0
000159 b70001 1 invokespecial #1
00015c b1 4 return
表示這個(gè)方法的代碼對應(yīng)的JVM指令,00000005代表命令有5個(gè)字節(jié)
接下來:
00015d 0000 0 exception table entries:
定義了使用的異常,0000代表沒有定義拋出的異常
接下來是方法存在的一些代碼屬性的描述,描述的是代碼本身的一些額外信息
00015f 0001 1 code attributes:
code attribute 0:
000161 000a name = #10<LineNumberTable>
000163 00000006 length = 6
Line number table:
000167 0001 length = 1
000169 00000008 start pc: 0 line number: 8
0001代表只有一行對應(yīng)關(guān)系,指向0000和0008,0000指向運(yùn)行時(shí)的行指針,0008指向?qū)嶋H源代碼的行數(shù),每個(gè)都是兩個(gè)字節(jié),所以可知Java代碼最大行數(shù)到65535,超過class就沒辦法表示出來了
Javap生成class文件
之前所有介紹的內(nèi)容都是靠oolong生成的class文件的二進(jìn)制表示,還可以通過JDK自帶的javap來生成class文件,而且更易理解:
javap -verbose ClassLearn > ClassLearn.txt
查看輸出:
1 Classfile /Users/lkc/Desktop/Code/Learn/out/production/TechCollect/ClassLearn.class
2 Last modified 2017-8-7; size 672 bytes
3 MD5 checksum 733971818278fd4709847bba1a9720bd
4 Compiled from "ClassLearn.java"
5 public class ClassLearn
6 minor version: 0
7 major version: 52
8 flags: ACC_PUBLIC, ACC_SUPER
9 Constant pool:
10 #1 = Methodref #6.#26 // java/lang/Object."<init>":()V
11 #2 = String #27 // hello,world!
12 #3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
13 #4 = Methodref #30.#31 // java/io/PrintStream.println:(Ljava/lang/String;)V
14 #5 = Class #32 // ClassLearn
15 #6 = Class #33 // java/lang/Object
16 #7 = Utf8 <init>
17 #8 = Utf8 ()V
18 #9 = Utf8 Code
19 #10 = Utf8 LineNumberTable
20 #11 = Utf8 LocalVariableTable
21 #12 = Utf8 this
22 #13 = Utf8 LClassLearn;
23 #14 = Utf8 main
24 #15 = Utf8 ([Ljava/lang/String;)V
25 #16 = Utf8 i
26 #17 = Utf8 I
27 #18 = Utf8 args
28 #19 = Utf8 [Ljava/lang/String;
29 #20 = Utf8 str
30 #21 = Utf8 Ljava/lang/String;
31 #22 = Utf8 StackMapTable
32 #23 = Class #34 // java/lang/String
33 #24 = Utf8 SourceFile
34 #25 = Utf8 ClassLearn.java
35 #26 = NameAndType #7:#8 // "<init>":()V
36 #27 = Utf8 hello,world!
37 #28 = Class #35 // java/lang/System
38 #29 = NameAndType #36:#37 // out:Ljava/io/PrintStream;
39 #30 = Class #38 // java/io/PrintStream
40 #31 = NameAndType #39:#40 // println:(Ljava/lang/String;)V
41 #32 = Utf8 ClassLearn
42 #33 = Utf8 java/lang/Object
43 #34 = Utf8 java/lang/String
44 #35 = Utf8 java/lang/System
45 #36 = Utf8 out
46 #37 = Utf8 Ljava/io/PrintStream;
47 #38 = Utf8 java/io/PrintStream
48 #39 = Utf8 println
49 #40 = Utf8 (Ljava/lang/String;)V
50 {
51 public ClassLearn();
52 descriptor: ()V
53 flags: ACC_PUBLIC
54 Code:
55 stack=1, locals=1, args_size=1
56 0: aload_0
57 1: invokespecial #1 // Method java/lang/Object."<init>":()V
58 4: return
59 LineNumberTable:
60 line 8: 0
61 LocalVariableTable:
62 Start Length Slot Name Signature
63 0 5 0 this LClassLearn;
64
65 public static void main(java.lang.String[]);
66 descriptor: ([Ljava/lang/String;)V
67 flags: ACC_PUBLIC, ACC_STATIC
68 Code:
69 stack=2, locals=3, args_size=1
70 0: ldc #2 // String hello,world!
71 2: astore_1
72 3: iconst_0
73 4: istore_2
74 5: iload_2
75 6: iconst_3
76 7: if_icmpge 23
77 10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
78 13: aload_1
79 14: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
80 17: iinc 2, 1
81 20: goto 5
82 23: return
83 LineNumberTable:
84 line 10: 0
85 line 11: 3
86 line 12: 10
87 line 11: 17
88 line 14: 23
89 LocalVariableTable:
90 Start Length Slot Name Signature
91 5 18 2 i I
92 0 24 0 args [Ljava/lang/String;
93 3 21 1 str Ljava/lang/String;
94 StackMapTable: number_of_entries = 2
95 frame_type = 253 /* append */
96 offset_delta = 5
97 locals = [ class java/lang/String, int ]
98 frame_type = 250 /* chop */
99 offset_delta = 17
100 }
101 SourceFile: "ClassLearn.java"
三個(gè)地方LineNumberTable,LocalVariableTable,StackMapTable
LineNumberTable
LineNumberTable下面有多個(gè)line a:b,每個(gè)line表示一行,a表示在類文件的第幾行,b表示在JVM指令中的pc偏移量
拿line 14:23舉例,14就對應(yīng)return這一句,23代表偏移地址,也就是指:
82 23: return
可以發(fā)現(xiàn)是一致的
LocalVariableTable
89 LocalVariableTable:
90 Start Length Slot Name Signature
91 5 18 2 i I
92 0 24 0 args [Ljava/lang/String;
93 3 21 1 str Ljava/lang/String;
可以很容易看出,LocalVariableTable由五部分組成,Start和Length表示變量有效作用域的偏移地址;Start表示變量被賦值到某個(gè)Slot中的指令的下一條指令的偏移地址;Length表示變量作用域總共占用的指令數(shù)對應(yīng)的偏移量,所以作用域就是[Start,Start+Length];Slot表示該變量占用的Slot編號;Name表示該變量的名稱;Signature表示該變量的類型
拿出一個(gè)來舉例:
90 Start Length Slot Name Signature
91 5 18 2 i I
這里就表示變量"i"的類型是int,作用域是[5,23],也就是這一段:
74 5: iload_2
75 6: iconst_3
76 7: if_icmpge 23
77 10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
78 13: aload_1
79 14: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
80 17: iinc 2, 1
81 20: goto 5
82 23: return
與實(shí)際代碼比較,也就是整個(gè)for循環(huán)體直到方法結(jié)束,所以符合我們預(yù)期;還需要提一句的是其實(shí)Slot是可以出現(xiàn)重復(fù)的,只要實(shí)際代碼中作用域不重合Slot的編號是可以復(fù)用的.
StackMapTable
這里在我閱讀本書時(shí)發(fā)現(xiàn)沒有StackMapTable,因此特地查了下資料
這里是由Java6引入的一個(gè)叫做棧圖的概念,Java7中開始強(qiáng)制使用,實(shí)例說明:
94 StackMapTable: number_of_entries = 2
95 frame_type = 253 /* append */
96 offset_delta = 5
97 locals = [ class java/lang/String, int ]
98 frame_type = 250 /* chop */
99 offset_delta = 17
number_of_entries
代表frame的個(gè)數(shù),這里也就是2個(gè),分別是frame_type = 253 /* append */
和 frame_type = 250 /* chop */
, 這里注釋的append
就代表?xiàng)V芯植孔兞炕蝾愒黾?個(gè),chop
相反代表減少一個(gè),還有一個(gè)same
代表沒有變化;offset_delta代表每個(gè)frame的增量偏移量,第一個(gè)的偏移量就是offset_delta本身,其他幀的偏移量計(jì)算方法是前一幀的偏移量加上此幀的offset_delta+1;目前僅了解這么多,想詳細(xì)了解StackMapTable相關(guān)或者其他的class文件信息可以參照< Java虛擬機(jī)規(guī)范 se7 >