main方法的執行流程
image-20230224180340262
1:先把檔案編譯成class檔案
2:window下,java.exe調用jvm.dll建立虛拟機,dll檔案相當于jar包。
3:c++建立引導類加載器。
4:執行個體化Launcher,由引導類加載器加載。
5:加載JvmTest.class檔案
6:執行main方法
loadClass加載流程,類是如何加載到java虛拟機的?
static int a = 10;
int b = 5;
1:加載:将位元組碼檔案丢到記憶體
2:驗證:需要驗證位元組碼格式是否正确。Cafe baba标準的位元組碼檔案開頭。
3:準備:對靜态變量進行指派,賦上預設值,将a賦為0。
4:解析:将符号引用轉變成直接引用,隻是将靜态的能夠确定被誰調用的才會被轉變,這個叫做靜态解析,類加載時候完成。
動态解析,在編譯的時候沒有辦法确定被誰調用,隻能在運作的時候才能判斷出來,比如多态。類運作時候完成。
符号引用包括三種:類、接口的符号引用。字段的符号引用。方法的符号引用。
直接引用:符号的記憶體位址。
5:初始話:将靜态遍變量真正指派,将a指派為10,靜态代碼塊也會在這裡執行 。
6:将class檔案加載到JVM虛拟機中。
類的加載機制一般是懶加載,在隻有用到的時候才會真正的加載。
public class JvmTest {
static int b = 10;
static {
System.out.println("jvmTest靜态方法");
System.out.println("b的值 = " + b);
}
public static void main(String[] args) {
System.out.println("執行main方法");
new A();
}
}
class A {
static {
System.out.println("A的靜态方法");
}
A() {
System.out.println("A的構造方法");
}
}
class B {
static {
System.out.println("B的靜态方法");
}
B(){
System.out.println("B的構造方法");
}
}
執行結果:
jvmTest靜态方法
b的值 = 10
執行main方法
A的靜态方法
A的構造方法
A的靜态代碼塊輸出是在main方法之後,也就是在真正使用的時候才會被加載。然後B沒有被使用,是以不會被加載。
類加載器和雙親委派機制
類加載器分為:
1:引導類加載器:是由c++建立,在jvm建立之後加載,它負責加載JRE下lib目錄的jar。
2:擴充類加載器:負責加載JER下lib的ext,extClassLoader
3:應用程式加載器:負責加載應用程式,appClassLoader
4:自定義加載器:自己定義的
System.out.println("String加載類:" + String.class.getClassLoader());
System.out.println("DESKeyFactory加載類:" + com.sun.crypto.provider.DESCipher.class.getClassLoader());
System.out.println("JvmTest加載類:" + JvmTest.class.getClassLoader());
輸出:
String加載類:null
DESKeyFactory加載類:sun.misc.Launcher$ExtClassLoader@372f7a8d
JvmTest加載類:sun.misc.Launcher$AppClassLoader@18b4aac2
String的加載類是引導類加載器加載的,引導類是由c++生成的,是以看不到,是個null。
extClassLoader是擴充類加載器。
AppClassLoader引用程式加載器。
Launcher
C++引導類加載完之後會執行個體化Launcher
class Launcher{
private ClassLoader loader;
private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
return launcher;
}
... ...
}
launcher是個靜态變量,是個單例,在加載的時候就初始話好了。
初始話的時候執行個體化ext和app ClassLoader
public Launcher() {
//執行個體化擴充類加載器,它的父類沒有設定值
Launcher.ExtClassLoader var1 =
Launcher.ExtClassLoader.getExtClassLoader();
//初始化APPclassLoader,并且将ext傳入設定為父類加載器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
}
loader指派為appClassLoader,預設先用appClassLoader加載
雙親委派機制
image-20230224132150805
C++在加載的時候調用
Launcher.getLauncher().getClassLoader().loadClass("JvmTest.class")
Launcher的classLoader是在執行個體化的時候指派了AppClassLoader。
1:AppClassLoader去自己已經加載的類裡邊找,如果沒有向上委托
2:ExtClassLoader去自己已經加載的類裡邊找,如果沒有向上委托。
3:引導類去自己加載的類裡邊找,如果沒有,
4:引導類嘗試加載,判斷這個類是不是應該由自己加載,判斷是不是JRE包路徑下的,如果是加載并且傳回,不是向下傳播
5:ExtClassLoader判斷這個類是不是ext包路徑下的,如果是自己加載并傳回,不是向下傳播
6:AppClassLoader判斷這個類是不是classPath路徑下的,如果是的化加載。不是的話,ClassNotFound
為什麼先由AppClassLoader,而不是引導類開始?
因為大部分類都是我們自己寫的,百分之95都是存放到AppClassLoader,隻有第一次加載的時候會多走一步,之後大部分都直接從AppClassLoader裡邊擷取。
loadClass("JvmTest.class")源碼
Class<?> loadClass(String name, boolean resolve){
//從自己加載的類裡邊找
Class<?> c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
//父加載器加載
c = parent.loadClass(name, false);
} else {
//ext的父是null,這是最後一層,引導類類加載器
c = findBootstrapClassOrNull(name);
}
if (c == null) {
//由URLClassLoader實作
c = findClass(name);
}
}
return c;
}
URLClassLoader.findClass源碼
Class<?> findClass(final String name){
String path = name.replace('.', '/').concat(".class");
//判斷是不是自己要加載的。jrt/ ext/
Resource res = ucp.getResource(path, false);
if (res != null) {
//真正加載class的方法
return defineClass(name, res);
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
新增一個String類
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println(111);
}
}
執行報錯資訊:
錯誤: 在類 java.lang.String 中找不到 main 方法, 請将 main 方法定義為:
public static void main(String[] args)
否則 JavaFX 應用程式類必須擴充javafx.application.Application
我們新增的 java.lang.String,是能夠在引導類加載器的JRE包下邊找到的,并且傳回String類,這個傳回的是jdk自帶的類,并不是我們自己寫的類,是以會報找不到main方法。
jdk為什麼要用雙親委派機制?
jdk不讓修改自己内部的類,沙箱安全機制,防止核心API被篡改。
避免類的重複加載,父加載器已經加載完了,自己就不用再加載了。
全盤負責:
public class JvmTest {
static User user;
}
當加載JvmTest類的時候,也會加載User類,這兩個類都會由同一個類加載器加載,不會由亂七八糟的加載器加載。
自定義加載器
我們要自定義加載器,隻需要實作ClassLoader,重寫findClass就可以。
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//真正的加載步驟
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader =
new MyClassLoader("C:/myClassLoader");
Class<?> clazz = myClassLoader.loadClass("com.bbk.code.User");
Object o = clazz.newInstance();
Method method = clazz.getDeclaredMethod("eat");
method.invoke(o);
System.out.println("目前MyClassLoader的類加載器:"+MyClassLoader.class.getClassLoader());
}
}
自定義classLoade繼承ClassLoader初始話會先初始化父類,在這時候會給自定義classLoader指派parent為AppClassLoader。具體代碼展現在:
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
... ...
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
... ...
return scl;
}
private static synchronized void initSystemClassLoader() {
... ...
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
scl = l.getClassLoader(); //傳回this.loader,引導類在加載Launcher的時候會指派為AppClassLoader
... ...
}
執行main方法,需要把User的class從target中删除,并且加入到我們自定義的檔案夾中。執行輸出
我是User類eat方法,我的加載器是com.bbk.code.MyClassLoader@5a07e868
如何打破雙親委派機制
意思就是不在委托父加載,直接自己加載類。雙親委派的邏輯代碼實作在loadClass方法中。需要繼承ClassLoader重寫loadClass方法即可:
image-20230224173458980
把紅框的代碼删除掉。
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
重新執行main方法,報錯:
java.io.FileNotFoundException: C:\myClassLoader\java\lang\Object.class (系統找不到指定的路徑。)
在加載User的時候,會現在父類Object,我們打破雙親委派之後,自定義的加載器不存在Object是以會報錯。
我們把Object.class放入到我們的本地檔案夾中。重新執行報錯:
//禁止加載java.lang 包
java.lang.SecurityException: Prohibited package name: java.lang
java.lang必須由我們引導類加載器加載,沙箱安全。我們隻能讓我們的引導類去加載java.lang。修改代碼
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
if(!name.startsWith("com.bbk")){
c = this.getParent().loadClass(name);
}else{
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
隻有當name是由com.bbk開頭的由我們加載器加載。其他的都由父加載器加載。不會報錯。