JDK (Java Development Kit) : java語言的軟件開發(fā)包。包括Java運行時環(huán)境JRE。
JRE (Java Runtime Environment) :Java運行時環(huán)境,包括JVM。
JVM (Java Virtual Machine) :一種用于計算機設備的規(guī)范。
Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。
實現(xiàn)通過類的權限定名獲取該類的二進制字節(jié)流的代碼塊叫做類加載器。
啟動類加載器(Bootstrap ClassLoader)用來加載 java 核心類庫,無法被 java 程序直接引用。
擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java虛擬機的實現(xiàn)會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
系統(tǒng)類加載器(system class loader):它根據(jù) Java 應用的類路徑(CLASSPATH) 來加載 Java類。一般來說,Java應用的類都是由它來完成加載的。可以通過ClassLoader.getSystemClassLoader()來獲取它。
用戶自定義類加載器,通過繼承 java.lang.ClassLoader 類的方式實現(xiàn)。
ClassLoader 顧名思義就是類加載器。Java源代碼首先被jvm編譯成.class文件。類從被加載到虛擬機內存中開始,直到卸載出內存為止,它的整個生命周期包括了:加載、驗證、準備、解析、初始化、使用和卸載這7個階段。其中,驗證、準備和解析這三個部分統(tǒng)稱為連接(linking)。
加載:通過一個類的全限定名來獲取定義此類的二進制字節(jié)流,將這個字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據(jù)結構,在內存中生成一個代表這個類的Class對象,作為方法去這個類的各種數(shù)據(jù)的訪問入口;
驗證:驗證是連接階段的第一步,這一階段的目的是確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬自身的安全;
準備:準備階段是正式為類變量分配內存并設置類變量初始值的階段,這些變量所使用的內存都將在方法去中進行分配。這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。
解析:解析階段是虛擬機將常量池內的符號(Class文件內的符號)引用替換為直接引用(指針)的過程。
初始化:初始化階段是類加載過程的最后一步,開始執(zhí)行類中定義的Java程序代碼(字節(jié)碼)。
父類靜態(tài)域——》子類靜態(tài)域——》父類成員初始化——》父類構造塊——》父類構造方法——》子類成員初始化——》子類構造塊——》子類構造方法;
類加載檢查:虛擬機遇到一條 new 指令時,首先檢查這個指令的參數(shù)是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執(zhí)行相應的類加載過程。
分配內存:在類加載檢查通過后,接下來虛擬機將為新生對象分配內存。對象所需的內存大小在類加載完成后便可確定,為對象分配空間的任務等同于把一塊確定大小的內存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
初始化零值:內存分配完成后,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應的零值。
設置對象頭:初始化零值完成之后,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的 GC 分代年齡等信息。 這些信息存放在對象頭中。 另外,根據(jù)虛擬機當前運行狀態(tài)的不同,如是否啟用偏向鎖等,對象頭會有不同的設置方式。
執(zhí)行init方法:在上面工作都完成之后,從虛擬機的視角來看,一個新的對象已經產生了,但從 Java 程序的視角來看,對象創(chuàng)建才剛開始,<init> 方法還沒有執(zhí)行,所有的字段都還為零。所以一般來說,執(zhí)行 new 指令之后會接著執(zhí)行 <init> 方法,把對象按照程序員的意愿進行初始化,這樣一個真正可用的對象才算完全產生出來。
1.如果一個類加載器收到了類加載請求,它并不會自己先加載,而是把這個請求委托給父類的加載器去執(zhí)行;
2.如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的引導類加載器;
3.如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成加載任務,子加載器才會嘗試自己去加載,這就是雙親委派機制;
避免類的重復加載,保護程序安全,防止核心API被隨意篡改。
JNDI(Java Naming and Directory Interface,Java命名和目錄接口)便是最典型的例子。JND需要調用由獨立廠商實現(xiàn)并部署在應用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代碼,但啟動類加載器不可能“認識”這些代碼那該怎么辦?
為了解決這個問題,Java設計團隊只好引入了一個不太優(yōu)雅的設計:線程上下文類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的 setContextClassLoaser()方法進行設置。有了線程上下文類加載器,JNDI服務使用這個線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,實際上已經違背了雙親委派模型的一般性原則。Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
例如JDBC在rt里面定義了這個SPI,那MySQL有MySQL的JDBC實現(xiàn),Oracle有Oracle的JDBC實現(xiàn),反正我java不管你內部如何實現(xiàn)的,反正你們都得統(tǒng)一按我這個來,這樣我們java開發(fā)者才能容易的調用數(shù)據(jù)庫操作。所以因為這樣那就不得不違反這個約束啊,Bootstrap ClassLoader就得委托子類來加載數(shù)據(jù)庫廠商們提供的具體實現(xiàn)。因為它的手只能摸到<JAVA_HOME>\lib中,其他的它無能為力,這就違反了自下而上的委托機制了。
不可以。因為在類加載中,會根據(jù)雙親委派機制去尋找當前java.lang.String是否已被加載。由于啟動類加載器已在啟動時候加載了所以不會再次加載,因此使用的String是已在java核心類庫加載過的String,而不是新定義的String。
Java源程序.java通過編譯器編譯成字節(jié)碼.class文件,也就是計算機可以識別的二進制文件。
根據(jù) Java 虛擬機規(guī)范的規(guī)定,class 文件格式采用一種類似于 C 語言的偽結構來存儲數(shù)據(jù),這種偽結構中只有兩種數(shù)據(jù)類型:無符號數(shù)和表。
無符號數(shù)屬于基礎數(shù)據(jù)類型,以 u1、u2、u4、u8 來分別代表 1 個字節(jié)、2 個字節(jié)、4 個字節(jié)和 8 個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照 UTF-8 編碼構成的字符串值。
表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構成的復合數(shù)據(jù)結構,所有表都習慣性地以 _info 結尾。表用于描述有層次關系的復合結構的數(shù)據(jù),整個 class 文件本質上就是一張表。
Java語言提供了對象終止(finalization)機制來允許開發(fā)人員提供對象被銷毀之前的自定義處理邏輯。當垃圾回收器發(fā)現(xiàn)沒有引用指向一個對象,即:垃圾回收此對象之前,總會先調用這個對象的finalize()方法,finalize()只會被調用一次。finalize() 方法允許在子類中被重寫,用于在對象被回收時進行資源釋放。通常在這個方法中進行一些資源釋放和清理的工作,比如關閉文件、套接字和數(shù)據(jù)庫連接等。
在JVM中表示兩個class對象是否為同一個類存在兩個必要條件:
1)類的完整類名必須一致,包括包名。
2)加載這個類的ClassLoader(指ClassLoader實例對象)必須相同。
主動使用,分為七種情況:
1)創(chuàng)建類的實例
2)訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值
3)調用類的靜態(tài)方法I
4)反射(比如:Class.forName(“com.atguigu.Test”))
5)初始化一個類的子類
6)Java虛擬機啟動時被標明為啟動類的類
7)JDK7開始提供的動態(tài)語言支持:java.lang.invoke.MethodHandle實例的解析結果REF getStatic、REF putStatic、REF invokeStatic句柄對應的類沒有初始化,則初始化
除了以上七種情況,其他使用Java類的方式都被看作是對類的被動使用,都不會導致類的初始化。
淺拷貝:只是增加了一個指針指向已存在的內存地址,
深拷貝:是增加了一個指針并且申請了一個新的內存,使這個增加的指針指向這個新的內存,使用深拷貝的情況下,釋放內存的時候不會因為出現(xiàn)淺拷貝時釋放同一個內存的錯誤。
淺復制:僅僅是指向被復制的內存地址,如果原地址發(fā)生改變,那么淺復制出來的對象也會相應的改變。
深復制:在計算機中開辟一塊新的內存地址用于存放復制的對象。
java -XX:+TraceClassLoading 具體類
Java -verbose 具體類