JAVA程序的運(yùn)行原理:
1. 源文件(.java源代碼)通過編譯器編譯成字節(jié)碼文件class。
2. 通過JVM中的解釋器將字節(jié)碼文件生成對(duì)應(yīng)的可執(zhí)行文件,運(yùn)行。
- 將編譯后的程序加載到方法區(qū),存儲(chǔ)類信息。
4. 運(yùn)行時(shí),JVM創(chuàng)建線程來執(zhí)行代碼,在虛擬機(jī)棧和程序計(jì)數(shù)器分配獨(dú)占的空間。根據(jù)方法區(qū)里的指令碼,在虛擬機(jī)棧對(duì)線程進(jìn)行操作,程序計(jì)數(shù)器保存線程代碼執(zhí)行到哪個(gè)位置。
“一處編寫,處處運(yùn)行”:編譯后,不依賴于平臺(tái)環(huán)境,在各種操作系統(tǒng)均可運(yùn)行。
Class字節(jié)碼文件:是一個(gè)二進(jìn)制文件,包含了JAVA程序執(zhí)行的字節(jié)碼,包含的信息有版本、訪問標(biāo)志、常量池、當(dāng)前類、超級(jí)類、接口、字段、方法、屬性等,中間沒有任何分隔符,文件開頭有一個(gè)特殊標(biāo)志,用16進(jìn)制表示為0xcafebabe。
Class文件反編譯后,可通過“JVM指令碼表”查看指令,這些指令運(yùn)行時(shí)都保存在方法區(qū)。
從指令碼中看出,沒有定義構(gòu)造函數(shù)時(shí),會(huì)有隱式的無參構(gòu)造函數(shù);方法參數(shù)保存在虛擬機(jī)棧里的本地變量表,每一個(gè)main方法都有一個(gè)String數(shù)組參數(shù),保存在本地變量表的變量0。
JVM運(yùn)行時(shí)數(shù)據(jù)區(qū):包含線程共享部分和線程獨(dú)占部分。
線程共享:線程共同訪問的內(nèi)存數(shù)據(jù)空間,隨著JVM(虛擬機(jī))或者GC(垃圾回收)而創(chuàng)建和銷毀。包含方法區(qū)和堆內(nèi)存。
1、方法區(qū):JVM用來存儲(chǔ)加載的類信息、常量、靜態(tài)變量、編譯后的代碼等數(shù)據(jù)。在JVM規(guī)范中,這是一個(gè)邏輯區(qū),根據(jù)不同的虛擬機(jī)有不同的具體實(shí)現(xiàn),如oracle的HotSpot的方法區(qū),在java7中放在永久代,java8中放在元數(shù)據(jù)空間,并通過GC機(jī)制對(duì)這個(gè)區(qū)域進(jìn)行管理。目前有三大Java虛擬機(jī):HotSpot,oracle JRockit,IBM J9。
2、堆內(nèi)存:存放對(duì)象的實(shí)例,在JVM啟動(dòng)時(shí)創(chuàng)建??杉?xì)分為老年代、新生代,垃圾回收器主要就是管理堆內(nèi)存,如果滿了就會(huì)出現(xiàn)OOM(OutOfMemoryError)。
線程獨(dú)占:每個(gè)線程都會(huì)有自己獨(dú)立的空間,隨著線程的生命周期而創(chuàng)建和銷毀。包含虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器。
1、虛擬機(jī)棧:即虛擬機(jī)執(zhí)行JAVA代碼的棧,每個(gè)線程都會(huì)在這有一個(gè)私有空間。線程棧由多個(gè)棧幀(Stack Frame)組成,一個(gè)線程會(huì)執(zhí)行一個(gè)或多個(gè)方法,一個(gè)方法對(duì)應(yīng)一個(gè)棧幀。棧幀內(nèi)容包括:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法返回地址、附加信息等。棧內(nèi)存默認(rèn)最大是1M,超出則拋StackOverflowError。
2、本地方法棧:即虛擬機(jī)執(zhí)行Native本地方法的棧,和虛擬機(jī)棧的主要區(qū)別是執(zhí)行的方法不同。在虛擬機(jī)規(guī)范沒有規(guī)定具體的實(shí)現(xiàn),由不同的虛擬機(jī)廠商去實(shí)現(xiàn)。HotSpot虛擬機(jī)中虛擬機(jī)棧和本地方法棧的實(shí)現(xiàn)方式是一樣的,超出大小后也會(huì)拋出StackOverflowError。
3、程序計(jì)數(shù)器:記錄當(dāng)前線程執(zhí)行字節(jié)碼的位置,存儲(chǔ)的是字節(jié)碼指定地址,如果執(zhí)行Native方法,則計(jì)數(shù)器值為空。每個(gè)線程在這都有一個(gè)私有空間,占用很少的內(nèi)存空間。
CPU同一時(shí)間只會(huì)執(zhí)行一條線程中的指令,JVM多線程會(huì)輪流切換并分配CPU執(zhí)行時(shí)間,在線程切換后,需要通過程序計(jì)數(shù)器來回復(fù)正確的執(zhí)行位置。
查看class文件內(nèi)容
使用Demo.Java進(jìn)行測(cè)試, 運(yùn)行javac Demo.java編譯成class文件, 然后運(yùn)行javap -v Demo.class > Demo.txt查看class文件內(nèi)容
Demo.Java
public class Demo{
public static void main(String[] args){
int x = 500;
int y = 100;
int a = x / y;
int b = 50;
System.out.println(a + b);
}
}
Demo.txt
Classfile /E:/*/Demo.class
Last modified 2019-6-30; size 412 bytes
MD5 checksum efd785af33e58aa9fc9834110b74b87b
Compiled from "Demo.java"
public class Demo
minor version: 0 //次版本號(hào)
major version: 52 //主版本號(hào) 版本號(hào)規(guī)則: JDK5,6,7,8分別對(duì)應(yīng)49,50,51,52
flags: ACC_PUBLIC, ACC_SUPER //訪問標(biāo)志
Constant pool: // 常量池 類信息包含的靜態(tài)常量, 編譯之后就能確認(rèn)
#1 = Methodref #5.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V
#4 = Class #19 // Demo
#5 = Class #20 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 main
#11 = Utf8 ([Ljava/lang/String;)V
#12 = Utf8 SourceFile
#13 = Utf8 Demo.java
#14 = NameAndType #6:#7 // "<init>":()V
#15 = Class #21 // java/lang/System
#16 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
#17 = Class #24 // java/io/PrintStream
#18 = NameAndType #25:#26 // println:(I)V
#19 = Utf8 Demo
#20 = Utf8 java/lang/Object
#21 = Utf8 java/lang/System
#22 = Utf8 out
#23 = Utf8 Ljava/io/PrintStream;
#24 = Utf8 java/io/PrintStream
#25 = Utf8 println
#26 = Utf8 (I)V
{
public Demo(); // 默認(rèn)隱式無參的構(gòu)造函數(shù)
descriptor: ()V
flags: 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 1: 0
public static void main(java.lang.String[]); //程序的入口main方法
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC //訪問控制
Code:
stack=3, locals=5, args_size=1 //方法棧棧幀中操作數(shù)棧的深度,本地變量數(shù)量,參數(shù)數(shù)量
0: sipush 500 //Jvm執(zhí)行引擎執(zhí)行這些源碼編譯過后的指令碼, javap翻譯出來的
3: istore_1 //是操作符, class 文件內(nèi)存儲(chǔ)的是指令碼, 前面的數(shù)據(jù)是偏移量,
4: bipush 100 //Jvm根據(jù)這個(gè)去區(qū)分不同的指令. 詳情參照'JVM指令碼表'
6: istore_2
7: iload_1
8: iload_2
9: idiv
10: istore_3
11: bipush 50
13: istore 4
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_3
19: iload 4
21: iadd
22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
25: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 7
line 6: 11
line 7: 15
line 8: 25
}
SourceFile: "Demo.java"
程序完整運(yùn)行分析
