帶着問題,尤爲是面試問題的學習纔是最高效的。加油,奧利給!java
點贊+收藏 就學會系列,文章收錄在 GitHub JavaEgg ,N線互聯網開發必備技能兵器譜git
Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的加載機制。Class文件由類裝載器裝載後,在JVM中將造成一份描述Class結構的元信息對象,經過該元信息對象能夠獲知Class的結構信息:如構造函數,屬性和方法等,Java容許用戶藉由這個Class相關的元信息對象間接調用Class對象的功能,這裏就是咱們常常能見到的Class類。github
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載七個階段。(驗證、準備和解析又統稱爲鏈接,爲了支持Java語言的運行時綁定,因此解析階段也能夠是在初始化以後進行的。以上順序都只是說開始的順序,實際過程當中是交叉的混合式進行的,加載過程當中可能就已經開始驗證了)面試
java.lang.Class
對象,做爲方法區這個類的各類數據的訪問入口加載 .calss
文件的方式數據庫
目的在於確保Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全bootstrap
主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證api
爲類變量分配內存而且設置該類變量的默認初始值,即零值數組
數據類型 | 零值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
char | '\u0000' |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
這裏不包含用final修飾的static,由於final在編譯的時候就會分配了,準備階段會顯示初始化tomcat
這裏不會爲實例變量分配初始化,類變量會分配在方法區中,而實例變量是會隨着對象一塊兒分配到Java堆中安全
private static int i = 1; //變量i在準備階只會被賦值爲0,初始化時纔會被賦值爲1
private final static int j = 2; //這裏被final修飾的變量j,直接成爲常量,編譯時就會被分配爲2
複製代碼
CONSTANT_Class_info
、CONSTANT_Fieldref_info
、CONSTANT_Methodref_info
等public class ClassInitTest{
private static int num1 = 30;
static{
num1 = 10;
num2 = 10; //num2寫在定義變量以前,爲何不會報錯呢??
System.out.println(num2); //這裡直接打印能夠嗎? 報錯,非法的前向引用,能夠賦值,但不可調用
}
private static int num2 = 20; //num2在準備階段就被設置了默認初始值0,初始化階段又將10改成20
public static void main(String[] args){
System.out.println(num1); //10
System.out.println(num2); //20
}
}
複製代碼
Java程序對類的使用方式分爲:主動使用和被動使用。虛擬機規範規定有且只有5種狀況必須當即對類進行「初始化」,即類的主動使用。
java.lang.invoke.MethodHandle
實例的解析結果,REF_getStatic
、REF_putStatic
、REF_invokeStatic
句柄對應的類沒有初始化,則初始化除以上五種狀況,其餘使用Java類的方式被看做是對類的被動使用,都不會致使類的初始化。
public class NotInitialization {
public static void main(String[] args) {
//只輸出SupperClass int 123,不會輸出SubClass init
//對於靜態字段,只有直接定義這個字段的類纔會被初始化
System.out.println(SubClass.value);
}
}
class SuperClass {
static {
System.out.println("SupperClass init");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("SubClass init");
}
}
複製代碼
JVM支持兩種類型的類加載器,分別爲引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)
從概念上來說,自定義類加載器通常指的是程序中由開發人員自定義的一類類加載器,可是Java虛擬機規範卻沒有這麼定義,而是將全部派生於抽象類ClassLoader的類加載器都劃分爲自定義類加載器
JAVA_HOME/jre/lib/rt.jar
、resource.jar
或sun.boot.class.path
路徑下的內容),用於提供JVM自身須要的類java.lang.ClassLoader
,沒有父加載器sun.misc.Launcher$ExtClassLoader
實現java.ext.dirs
系統屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext
子目錄(擴展目錄)下加載類庫。若是用戶建立的JAR 放在此目錄下,也會自動由擴展類加載器加載sun.misc.Lanucher$AppClassLoader
實現classpath
或系統屬性java.class.path
指定路徑下的類庫ClassLoader#getSystemClassLoader()
方法能夠獲取到該類加載器public class ClassLoaderTest {
public static void main(String[] args) {
//獲取系統類加載器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@135fbaa4
//獲取其上層:擴展類加載器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@2503dbd3
//再獲取其上層:獲取不到引導類加載器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader); //null
//對於用戶自定義類來講,默認使用系統類加載器進行加載,輸出和systemClassLoader同樣
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@135fbaa4
//String 類使用引導類加載器進行加載。Java的核心類庫都使用引導類加載器進行加載,因此也獲取不到
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1); //null
//獲取BootstrapClassLoader能夠加載的api的路徑
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url.toExternalForm());
}
}
}
複製代碼
在Java的平常應用程序開發中,類的加載幾乎是由3種類加載器相互配合執行的,在必要時,咱們還能夠自定義類加載器,來定製類的加載方式
java.lang.ClassLoader
類的方式,實現本身的類加載器,以知足一些特殊的需求ClassLoader類,是一個抽象類,其後全部的類加載器都繼承自ClassLoader(不包括啓動類加載器)
方法 | 描述 |
---|---|
getParent() | 返回該類加載器的超類加載器 |
loadClass(String name) | 加載名稱爲name的類,返回java.lang.Class類的實例 |
findClass(String name) | 查找名稱爲name的類,返回java.lang.Class類的實例 |
findLoadedClass(String name) | 查找名稱爲name的已經被加載過的類,返回java.lang.Class類的實例 |
defineClass(String name, byte[] b, int off, int len) | 把字節數組b中內容轉換爲一個Java類,返回java.lang.Class類的實例 |
resolveClass(Class<?> c) | 鏈接指定的一個Java類 |
JVM必須知道一個類型是由啓動加載器加載的仍是由用戶類加載器加載的。若是一個類型是由用戶類加載器加載的,那麼JVM會將這個類加載器的一個引用做爲類型信息的一部分保存在方法區中。當解析一個類型到另外一個類型的引用的時候,JVM須要保證這兩個類型的類加載器是相同的。
Java虛擬機對class文件採用的是按需加載的方式,也就是說當須要使用該類的時候纔會將它的class文件加載到內存生成class對象。並且加載某個類的class文件時,Java虛擬機採用的是雙親委派模式,即把請求交給父類處理,它是一種任務委派模式。
在JVM中表示兩個class對象是否爲同一個類存在兩個必要條件:
若是咱們自定義String類,可是在加載自定義String類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程當中會先加載jdk自帶的文件(rt.jar包中java\lang\String.class),報錯信息說沒有main方法就是由於加載的是rt.jar
包中的String類。這樣就能夠保證對java核心源代碼的保護,這就是簡單的沙箱安全機制。
Thread.setContextClassLoaser()
設置該類加載器,而後頂層ClassLoader再使用Thread.getContextClassLoader()
得到底層的ClassLoader進行加載。java.lang.LinkageError
。當咱們想要實現代碼熱部署時,能夠每次都new一個自定義的ClassLoader來加載新的Class文件。JSP的實現動態修改就是使用此特性實現。參考:《深刻理解JVM虛擬機》《尚硅谷JVM》