前言
生成字節碼後,這些數據如何加載到jsm中,並怎麼存儲成爲了問題,本文主要研究一下這個內容。
加載到jvm 內存中
通過javac 轉換成.class 字節碼文件,這個時候計算機還是不能直接識別的,由jvm加載class文件,JVM的類加載是通過ClassLoader及其子類來完成的,再翻譯成二進制指令,Java字節碼的執行是由JVM解釋器引擎來完成,類的層次關係和加載順序可以由下圖來描述:
我們拆分這幾個區域大家一起看下:
-
加載
加載主要是將.class文件(也可以是zip包)通過二進制字節流讀入到JVM中。 在加載階段,JVM需要完成3件事:
1)通過classloader在classpath中獲取XXX.class文件,將其以二進制流的形式讀入內存。
2)將字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構;
3)在內存中生成一個該類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口。 -
連接:驗證
這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害到自身的安全,驗證大致會分爲4個階段的校驗動作:
(1)文件格式驗證:主要驗證字節流是否符合Class文件的規範
(2)元數據驗證:對字節碼描述的信息進行語義分析,以保證描述的信息符合java語言規範的要求。
(3)字節碼驗證:主要的目的是通過對數據流和控制流分析,確定程序語義是合法的、符合邏輯的。
(4)符號引用驗證:該驗證可以看作對類自身以外的信息進行匹配校驗。此驗證發生在下文的解析階段 -
連接:準備
正式爲類變量(其實是靜態變量)分配初始內存並設置初始值(並不是代碼的初始賦值)的階段,這些變量所使用的內存都將在方法區中進行分配 -
連接:解析
將虛擬機中常量池內的符號引用替換爲直接引用的過程,
(1)符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能夠無歧義定位到目標即可。
(2)直接引用:直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。
解析過程是也可以在運行過程中發生,這個跟動態語言調用相關。 -
初始化
初始化是類加載的最後一步,前面的類加載的過程中,除了在加載階段用戶應用程序可以通過自定義類加載器參與外,其餘動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的java程序代碼。它主要是負責:初始化階段是執行類構造器< clinit >()方法的過程, < clinit >()是編譯器自動收集類中所有的類變量的賦值動作、靜態代碼塊產生的。 -
使用
當有繼承關係時,先初始化父類再初始化子類,所以創建一個子類時其實內存中存在兩個對象實例。 -
卸載
即銷燬一個對象,一般情況下中有JVM垃圾回收器完成。代碼層面的銷燬只是將引用置爲null。
類加載器
類加載器是用來加載字節碼文件的,主要分爲:
-
啓動類加載器(Bootstrap ClassLoader):這個加載器使用C++語言實現,負責加載虛擬機啓動所需要的類庫。它只能加載自己能夠識別的類。
-
擴展類加載器(Extendtion ClassLoader):它負責加載<JAVA_HOME>\lib\ext目錄中的或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以使用擴展類加載器。繼承自抽象類java.lang.ClassLoader。
-
應用程序類加載器(Application ClassLoader):負責加載CLassPath上所指定的類庫,如果應用程序沒有自定義過自己的類加載器,一般情況下這就是程序的默認類加載器。繼承自抽象類java.lang.ClassLoader。
-
自定義類加載器(User ClassLoader):自己定義的類加載器,繼承自抽象類java.lang.ClassLoader。
雙親委派模型的工作過程:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,因此所有的加載請求最終都會委派到啓動類加載器中。只有父類加載器反饋自己無法加載這個類時,子類加載器纔會嘗試自己去加載。
new一個對象
-
檢查這個指令的參數是否能在常量池中能否定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。去方法區 找這個對象的
-
爲對象分配空間的任務等同於把一塊確定大小的內存從Java堆中劃分出來,方法有指針碰撞、空閒列表
-
虛擬機需要將分配到的內存空間中的數據類型都初始化爲零值(不包括對象頭);接下來虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息,這些信息都存放在對象的對象頭中。
一個class 有那些佔用內存呢?
- 對象頭:16字節,markword 8字節,kclass:8字節
- 對象類字段、父類字段:具體的字段按照具體的值來弄
- 對齊填充:整體對象是8的倍數
問題:靜態代碼塊、普通代碼塊、構造函數執行順序
- 靜態代碼塊:這個先執行,因爲它是在類加載的過程中就執行。
- 普通代碼塊:再就是普通代碼塊執行,對象一建立就運行構造代碼塊了,而且優先於構造函數執行。
- 構造函數:最後就是構造函數,對象一建立,就會調用與之相應的構造函數。
問題:靜態類、靜態內部類加載
如果一個類要被聲明爲static的,只有一種情況,就是靜態內部類,就像靜態方法一樣被聲明。靜態有一些特點:
1.全局唯一,任何一次的修改都是全局性的影響
2.只加載一次,優先於非靜態
3.使用方式上不依賴於實例對象。
4.生命週期屬於類級別,從JVM 加載開始到JVM卸載結束。
-
什麼時候加載呢?
靜態內部類在使用的時候纔會加載,可以參考博客測試一下,但是我覺得爲啥爲啥不跟靜態變量一樣直接使用呢? -
適用場景
靜態內部類使用場景一般是當外部類需要使用內部類,而內部類無需外部類資源,並且內部類可以單獨創建的時候會考慮採用靜態內部類的設計
代碼執行
可以參考這個博客:Java程序編譯和運行的過程