說點什么呢,java比你想的要難
寫了多年java,發現好多人並不知道一個class文件怎么被解析執行的,所以我也發表下看法
1. 編寫java源文件
2. 把java源文件編譯成.class字節碼文件,JVM不認識源文件
3. JVM處理class文件
搞java開發,不得不提的就是JVM了,JVM全稱是Java Virtual Machine(簡稱JVM,中文叫Java虛擬機,請務必記住JVM,看到不少人整天JVM的,都不知道它的全稱是什么),java的宿主環境,可以認為JVM就是虛擬仿真出來的一台計算機。簡單繪了一張圖,如下(一圖勝千言):
java之所以一次編寫,到處運行,就是因為虛擬機(虛擬虛擬,虛擬出來的計算機,一台被托管的電腦)的緣故。
3.1 jvm處理class文件
加載是指將java源文件編譯之后的class文件讀入到內存中,然后在堆區創建一個java.lang.Class對象,用於封裝類在方法區內的數據結構。類加載的最終目的是封裝類在方法區的數據結構,並向java程序員提供訪問方法區數據的接口。
類的生命周期一共分為5個階段,加載、連接、初始化、使用、卸載。
加載:類的加載過程主要完成3件事件,1.通過類的全限定名來獲取定義此類的二進制字節流,2.將這個類字節流代表的靜態存儲結構轉為方法區的運行時數據結構,3.在堆中生成一個代表此類的java.lang.Class對象,作為訪問方法區這些數據結構的入口。這個過程主要是類加載器完成的,如圖
從上圖我們就可以看出類加載器之間的父子關系和管轄范圍。
BootStrap是最頂層的類加載器,它是由C++編寫而成,並且已經內嵌到JVM中了,主要用來讀取Java的核心類庫JRE/lib/rt.jar
ExtensionClassLoader是是用來讀取Java的擴展類庫,讀取JRE/lib/ext/*.jar
AppClassLoader是用來讀取CLASSPATH指定的所有jar包或目錄的類文件
甚至可以自定義加載器
加載過程用到了很牛掰的雙親委派模型,它是這樣的一套機制:
"類加載器"加載類時,先判斷該類是否已經加載過了;
如果還未被加載,則首先委托其"類加載器"的"父類加載器"去加載該類,這是一個向上不斷搜索的過程,當類所有的"祖宗類加載器"(包括了bootstrap classloader)都沒有加載到類,則回到發起者"類加載器"去加載,如果還加載不了,則拋出ClassNotFoundException.
連接:這個過程分3個階段(校驗,准備,解析)完成。首先是校驗,此階段主要校驗class文件包含的信息是否符合jvm的規范。具體的校驗通過對文件格式,元數據,字節碼,符號引用驗證來完成。然后是准備,此階段為類變量分配內存,並將其初始化為默認值。最后是解析,即把類型中的符號引用轉換成為直接引用。具體的解析有4種,1.類或接口的解析,2.字段解析,3.類方法解析,4.接口方法解析。完成這3個階段就完成了類的連接。
初始化(很重要):即執行類的構造器方法的過程。有5種方法可以完成初始化:1.調用new方法,2.使用Class類的newInstance方法(反射機制),3.使用Constructor類的newInstance方法(反射機制),4.使用Clone方法創建對象,5.使用(反)序列化機制創建對象
碼農開發用new關鍵字創建對象,而框架(spring,mybatis等)特別喜歡用第2和3種方式創建對象,還記得我說的框架四要素嗎,其中有一要素就是反射機制
使用:完成類的初始化后,就可以對類進行實例化,在程序中進行使用了
卸載:當類被加載,連接和初始化后,它的生命周期就始了,當代表類的class對象不在被引用時,class對象就會結束生命周期,類在方法區內的數據就會被卸載。因此一個類何時結束生命,取決於代表它的class對象何時結束生命。
3.2 JVM的內存結構
Java程序在運行時,需要在內存中的分配空間。為了提高運算效率,就對數據進行了不同空間的划分,因為每一片區域都有特定的處理數據方式和內存管理方式,如上圖,Java中的內存分配了5個區:
Method Area方法區
方法區是被所有線程共享,所有字段和方法字節碼,以及一些特殊方法如構造函數,接口代碼也在此定義。簡單說,所有定義的方法的信息都保存在該區域,此區域屬於共享區間。
靜態變量+常量+類信息+運行時常量池存在方法區中,實例變量存在堆內存中。
Heap 堆:堆這塊區域是JVM中最大的,應用的對象和數據都是存在這個區域,這塊區域也是線程共享的,也是 gc 主要的回收區,一個 JVM 實例只存在一個堆類存,堆內存的大小是可以調節的。類加載器讀取了類文件后,需要把類、方法、常變量放到堆內存中,以方便執行器執行。
注jdk8永久代改為了metaspace元空間
Stack 棧:棧也叫棧內存,主管Java程序的運行,是在線程創建時創建,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來說不存在垃圾回收問題,只要線程一結束該棧就Over,生命周期和線程一致,是線程私有的, 基本類型的變量和對象的引用變量都是在函數的棧內存中分配。遵循“先進后出”/“后進先出”原則。
PC Register程序計數器
每個線程都有一個程序計算器,就是一個指針,指向方法區中的方法字節碼(下一個將要執行的指令代碼),由執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不記。
Native Method Stack本地方法棧
它的具體做法是Native Method Stack中登記native方法,在Execution Engine執行時加載native libraies。
暫且這么多,以后有時間了接着補充。。。。。。
參考:
0. The Java® Language Specification(8) https://docs.oracle.com/javase/specs/jls/se8/html/index.html
1. The Java® Virtual Machine Specification (8) https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
我想於java碼農而言,沒有比這這兩個官方上千頁的規范更重要的了,看清楚了:一個是Java語言規范,一個是Java虛擬機規范
當然還有其他規范為了方便碼農開發
2. Advanced Java Bytecode Tutorial https://www.jrebel.com/blog/java-bytecode-tutorial
3. 深入JVM:ClassLoader相關知識簡介 https://developer.51cto.com/art/201009/227269.htm
4. Java Class Loader https://javapapers.com/core-java/java-class-loader/
5. Understanding the Java ClassLoader https://www.ibm.com/developerworks/java/tutorials/j-classloader/j-classloader.html
6. Advanced Java Class Tutorial: A Guide to Class Reloading https://www.toptal.com/java/java-wizardry-101-a-guide-to-java-class-reloading
7. 看完這篇文章你還敢說你懂JVM嗎? https://virtual.51cto.com/art/201901/591418.htm?mobile
8. JVM internals https://blog.jamesdbloom.com/JVMInternals.html#jvm_system_threads
9. Difference between initializing a class and instantiating an object?
10. class-loader-subsystem-jvm-internals https://codepumpkin.com/class-loader-subsystem-jvm-internals/
參考書籍:
0. Java編程思想 (第1,,2,5,8,14章節)
1. Virtual Machines
抽象虛擬模仿一台計算機