說點什麼呢,java比你想的要難 寫了多年java,發現好多人並不知道一個class文件怎麼被解析執行的,所以我也發表下看法 1. 編寫java源文件 2. 把java源文件編譯成.class位元組碼文件,JVM不認識源文件 3. JVM處理class文件 搞java開發,不得不提的就是JVM ...
說點什麼呢,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
模仿一臺電腦