.java:儲存需要執行的程式邏輯 編譯 .class:儲存java代碼轉換後的虛拟機指令
要使用某個類時,虛拟機将加載.class檔案,并建立對應的class對象。
将class檔案加載到虛拟機的記憶體的過程叫類加載,過程如下:
①加載Loading:利用class檔案建立class對象
②驗證Verification:確定class檔案的位元組流中包含資訊符合虛拟機要求,不會危害虛拟機本身安全,包括四種驗證:檔案格式驗證;中繼資料驗證;位元組碼驗證;符号引用驗證。
③準備Preparation:為類變量(static修飾的變量)配置設定記憶體并設定該類變量的初始值0
(static int i = 5;将隻會把i初始化為0,5的值在初始化時指派)。注意:智利不包含用final修飾的static,因為final在編譯時就會配置設定了;這裡不會為執行個體變量配置設定初始化,連變量會配置設定在方法區中,而執行個體變量是會随着對象一起配置設定到java堆中。
④解析Resolution:将常量池中的符号引用替換為直接引用的過程。
⑤初始化Initialization:執行靜态初始化器和靜态初始化成員變量。
其中②③④被稱為連結(Linking)過程。
符号引用:符号引用就是字元串,這個字元串包含足夠的資訊,以供實際使用時可以找到相應的位置。你比如說某個方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。裡面有類的資訊,方法名,方法參數等資訊。當第一次運作時,要根據字元串的内容,到該類的方法表中搜尋這個方法。運作一次之後,符号引用會被替換為直接引用,下次就不用搜尋了。直接引用就是偏移量,通過偏移量虛拟機可以直接在該類的記憶體區域中找到方法位元組碼的起始位置。
直接引用:
(1)直接指向目标的指針(比如,指向“類型”【Class對象】、類變量、類方法的直接引用可能是指向方法區的指針)
(2)相對偏移量(比如,指向執行個體變量、執行個體方法的直接引用都是偏移量)
(3)一個能間接定位到目标的句柄
符号引用和直接引用的解釋:
https://www.zhihu.com/question/30300585 https://blog.csdn.net/kkdelta/article/details/17752097啟動(BootStrap)類加載器:
加載JVM自身需要的類,是虛拟機自身的一部分,會将JAVA_HOME/lib下的核心類庫或-Xbootclasspath參數指定路徑下的jar包加載到記憶體中(出于安全考慮,bootstrap類加載器隻加載包名為java、javax、sun等開頭的類)。
擴充(Extension)類加載器:
Sun公司實作的sum.misc.Luncher$ExtClassLoader類,是Luncher的靜态内部類,負責加載JAVA_HOME/lib/ext下或由系統變量-Djava.ext.dir指定路徑中的類庫,開發者可以直接使用标準擴充類加載器。
系統(System)類加載器:
也稱應用程式加載器,指sun公司實作的sum.misc.Launcher$AppClassLoader。負責加載系統類路徑java –classpath或 –Djava.class.path下的類庫。一般情況下該類加載器是程式預設的類加載器,可以通過ClassLoader#getSystemClassLoader()方法擷取到該類加載器。
雙親委派模式加載:

原理說明:一個類加載器收到了類加載請求,不會自己先加載,而是把這個請求委派給父類加載器去執行,如果父類還有父類則繼續向上委托,最終達到頂層的啟動類加載器,從啟動類加載器開始進行加載,如果成功則傳回,否則子加載器才會嘗試自己加載。
好處:
①防止重複加載:如果父加載器已經加載了此類子加載器就沒必要再加載了
②安全考慮:如果運作時要加載一個java.lang.Integer類,會傳遞到啟動類加載器,而啟動類加載器發現在核心java API中有這個類已被加載,就直接傳回已經加載了的Integer.class。再例如:要加載一個java.lang.FakeInteger,啟動,擴充類加載器的路徑下都沒有該類,不會加載,會反向委托給系統類加載器加載,但是這樣是不行的,因為java.lang是核心API包,需要通路權限,強制加載會報如下異常:
java.lang.SecurityException: Prohibited package name: java.lang
類加載器常見類:
通過上面的報錯堆棧,可以看出幾個ClassLoader相關類的關系如下:
①ClassLoader
抽象類,定義了幾個基本的類加載相關方法。
②SecureClassLoader
擴充了ClassLoader,新增了幾個與使用相關的代碼源和權限定義類驗證。
③URLClassLoader
實作了ClassLoader沒實作的幾個方法,如findClass 等,有一個相關類URLClassPath負責擷取Class位元組碼流,一般自定義類加載器繼承URLClassLoader即可。這兩個類的構造方法都有一個必填參數URL,該參數是一個路徑,可能是檔案或jar包,然後根據不同路徑建立FileLoader或JarLoader或預設的Loader去加載相應路徑下的class檔案。
類加載器常用方法:
①loadClass
②findClass
③defineClass
将byte位元組流解析成JVM能夠識别的Class對象,一般defineClass()方法通常與findClass()方法一起使用,一般在自定義類加載器時會覆寫ClassLoader類的findClass方法并編寫加載規則,取的要加載類的位元組碼後轉換成流,然後調用defineClass方法成成類的Class對象。
④resolveClass(Class c)
對Class對象進行解析。
ExtClassLoader和AppClassLoader
他們都繼承自URLClassLoader,是sun.misc.Launcher的靜态内部類。類結構如下:
類加載器關系:
①啟動類加載器:由C++實作,沒有父加載器
②擴充類加載器(ExtClassLoader)由Java實作,父加載器為null
③系統類加載器(AppClassLoader),父加載器為ExtClassLoader
④自定義類加載器,父加載器為AppClassLoader
類與類加載器
JVM中兩個class對象是否是統一個類對象的必要條件為:
①類的完整類名必須一緻,包括包名
②加載這個類的ClassLoader必須相同
實作自己的ClassLoader
public class MyClassLoader extends ClassLoader
{
private String classPath;
public MyClassLoader(){
super();
}
public MyClassLoader(String classPath){
this.classPath = classPath;
}
public static void main(String[] args)
{
try
{
String path = "F:\\Learn\\Java\\workspace\\spring\\MyClassLoader\\bin";
MyClassLoader mcl1 = new MyClassLoader(path); // 自定義類加載器執行個體1
Class clazz1 = mcl1.findClass("com.classloader.MyClassLoader"); // 自定義類加載器加載自身類
MyClassLoader mcl2 = new MyClassLoader(path); // 自定義類加載器執行個體2
Class clazz2 = mcl2.findClass("com.classloader.MyClassLoader"); // 自定義類加載器加載自身類
Object inst = clazz1.newInstance(); //new一個自身類的執行個體
System.out.println(inst.toString());
System.out.println(mcl1.getClass().hashCode());//系統預設加載器加載的class類的執行個體是一樣的
System.out.println(mcl2.getClass().hashCode());//系統預設加載器加載的class類的執行個體是一樣的
System.out.println(clazz1.hashCode());//自定義加載器兩次加載的class執行個體是不一樣的(使用findClass)
System.out.println(clazz2.hashCode());//自定義加載器兩次加載的class執行個體是不一樣的(使用findClass)
System.out.println(mcl1.loadClass("com.classloader.MyClassLoader").hashCode());//loadClass和上面findClass的class對象執行個體是一樣,因為loadClass時會先findClass,檢查類是否已經被加載
System.out.println(mcl2.loadClass("com.classloader.MyClassLoader").hashCode());//loadClass和上面findClass的class對象執行個體是一樣,因為loadClass時會先findClass,檢查類是否已經被加載
System.out.println(mcl1.getSystemClassLoader());//系統類加載器是AppClassLoader
System.out.println(mcl1.getParent());//自定義類加載器的父加載器是AppClassLoader
System.out.println(mcl1.getSystemClassLoader().getParent());//系統類加載器的父加載器是ExtClassLoader
System.out.println(mcl1.getSystemClassLoader().getParent().getParent());//ExtClassLoader的父加載器是null
} catch (Exception e)
{
e.printStackTrace();
}
}
@Override
public String toString()
{
return "MyClassLoader";
}
public Class<?> findClass(String name) throws ClassNotFoundException
{
// 擷取class檔案的byte[]數組
try
{
byte[] classBytes = findClassBytes(name);
if(null == classBytes){
throw new ClassNotFoundException();
}
return defineClass(name,classBytes,0,classBytes.length);
} catch (FileNotFoundException e)
{
e.printStackTrace();
}
return null;
}
public byte[] findClassBytes(String className) throws FileNotFoundException
{
String path = this.classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
InputStream is = null;
if(this.classPath.startsWith("http")){
try
{
/*URL u = new URL(path);
is = u.openStream();*/
} catch (Exception e)
{
e.printStackTrace();
}
}else{
is = new FileInputStream(path);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bs = new byte[1024];
int bytesNum = 0;
try
{
while((bytesNum = is.read(bs)) != -1){
baos.write(bs,0,bytesNum);
}
return baos.toByteArray();
} catch (IOException e)
{
e.printStackTrace();
}
finally{
try
{
is.close();
baos.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
return null;
}
}