JVM總括四-類載入過程、雙親委派模型、物件例項化過程 JVM思考-init和clinit區別
JVM總括四-類載入過程、雙親委派模型、物件例項化過程
目錄:JVM總括:目錄
一、 類載入過程
類載入過程就是將.class檔案轉化為Class物件,類例項化的過程,(User user = new User(); 這個過程是物件例項化的過程);
一個.class檔案只有一個Class物件(位元組碼物件),可以有無數個物件(例如:new User(););
1、Load:
將編譯後的.class檔案以二進位制流的方式載入到JVM記憶體中,並轉化為特定的資料結構,用到的就是classLoad二類載入器。這個過程中校驗cafe babe魔法數、常量池、檔案長度
2、Link:
分為驗證、準備、解析三步。
驗證:更為詳細的驗證,比如:final是否規範(二次賦值不規範)、static是否合理(靜態方法必須引用靜態變數)、型別是否正確。
準備:靜態變數分配記憶體並設定預設值(靜態變數不賦值是有個預設值得),這個過程沒有物件例項化。
解析:把類中的符號引用轉化為直接引用,完成記憶體結構佈局。(符號引用轉化為直接引用:例如:test1() { test2(); },這裡test1呼叫test2方法就是符號引用,但實際test2()通過一個指標指向test2方法的記憶體地址,這個指標負責呼叫,它就是直接引用)。
3、Init:
執行類構造器<clinit>,執行靜態程式碼塊,為靜態變數賦予正確的初始值,遞迴初始化父類,不執行建構函式。(先執行靜態程式碼塊,再執行靜態變數)。
任何小寫的class定義的類都有一個魔法屬性:class,
int j; Class<Integer> integerClass = int.class; Class<Integer> integerClass1 = Integer.class;
二、類載入的原則:雙親委派模型
類載入是怎麼確定類檔案的位置呢?
1、Bootstrap:最高階的類載入器,裝置最核心的類,如:Object、System、String;
2、Extension ClassLoader:JDK9之前的類載入器,以後的為Platform ClassLoader,載入系統的擴充套件類;
3、Application ClassLoader:應用類載入器,主要載入使用者自定義CLASSPATH路徑下的類。
類載入器並非繼承關係,只是以組合的方式服用父載入器功能,符合優先原則。
其中第二、第三層為java實現,使用者可以自定義類載入器,第三層為C++實現,所有為null:
ClassLoader classLoader = Son.class.getClassLoader(); ClassLoader parent = classLoader.getParent(); ClassLoader parent1 = parent.getParent(); System.out.println(classLoader); System.out.println(parent); System.out.println(parent1);
----------------------------------- [email protected] [email protected] null
獲取Bootstrap載入的類庫:
URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath(); URL[] urLs = bootstrapClassPath.getURLs(); for (URL url : urLs){ System.out.println(url.toExternalForm()); }
--------------------------------------------------------- file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/resources.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/rt.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/sunrsasign.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/jsse.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/jce.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/charsets.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/lib/jfr.jar file:/D:/jdk-8u151-windows-x64/JDK/jre/classes
Bootstrap載入的路徑可以追加,在JVM增加啟動引數,不過不建議修改:
Xbootclasspath/D:/test/src
自定義類載入器的情況:
1、隔離載入類:框架中吧類載入到不同的環境中
2、修改類載入方式:除了Bootstrap,其他類並非一定要加入。
3、擴充套件載入源:比如載入資料庫;
4、防止原始碼洩露:可以對類進行加密,那載入的時候需要自定義載入器對類進行解密載入。
自定義ClassLoder,繼承ClassLoader,重寫findClass(),呼叫defineClass():
public class UserClassLoader extends ClassLoader { private String classpath; public UserClassLoader(String classpath) { this.classpath = classpath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte [] classDate=getDate(name); if(classDate==null){ throw new FileNotFoundException(); } else{ //defineClass方法將位元組碼轉化為類 return defineClass(name,classDate,0,classDate.length); } } catch (IOException e) { e.printStackTrace(); } return super.findClass(name); } //返回類的位元組碼 private byte[] getDate(String className) throws IOException{ InputStream in = null; ByteArrayOutputStream out = null; String path=classpath + File.separatorChar +className.replace('.',File.separatorChar)+".class"; try { in=new FileInputStream(path); out=new ByteArrayOutputStream(); byte[] buffer=new byte[2048]; int len=0; while((len=in.read(buffer))!=-1){ out.write(buffer,0,len); } return out.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } finally{ in.close(); out.close(); } return null; } }UserClassLoader
呼叫,其中D:\\wp目錄下為.class檔案,裡面有個Son.class
//自定義類載入器的載入路徑 UserClassLoader myClassLoader=new UserClassLoader("D:\\wp"); //包名+類名 Class sonClass=myClassLoader.loadClass("com.Son"); if(sonClass!=null){ Object obj=sonClass.newInstance(); System.out.println(sonClass.getClassLoader().toString()); }
三、物件例項化
從位元組碼、執行步驟兩個方面分析
1、位元組碼方面:
Object object = new Object();,通過javap -verbose -p檢視物件建立的位元組碼指令:
(1)new:如果找不到Class物件就進行類載入,然後分配記憶體(本類路徑上所有的屬性都分配),其中物件的引用也是個變數也佔記憶體(4個位元組),這個指令執行完畢會把物件的壓入虛擬機器棧頂。
(2)dup:在棧頂複雜引用,如果<init>有引數,把引數壓入操作棧,兩個引用,壓入棧底的用來賦值或儲存到區域性變量表中,棧頂引用作為控制代碼呼叫相關方法。
(3)invokespecial:呼叫物件例項化方法,通過棧頂方法呼叫<init>方法(也就是呼叫構造方法)。
2、執行步驟
(1)確認類元資訊是否存在:接到new指令時,在metaspace檢查類元資訊是否存在,沒有就在雙親委派模式下進行類載入,生成Class物件。
(2)分配物件記憶體:首先計算物件佔用空間大小(成員變數是引用變數就分配4個位元組大小的變數空間),在堆中劃分記憶體空間給新物件(分配空間需要進行同步操作,如:cas)。
(3)設定預設值:成員變數設定不同形式的0值;
(4)設定物件頭:設定物件的雜湊碼、鎖資訊、物件所屬的類元資訊,設定取決於JVM。
(5)執行init方法:初始化成員變數,執行程式碼塊,呼叫類的構造方法,把堆物件首地址賦值給引用變數。
四、思考:ClassLoader.loadClasshe和Class.forName區別
1 //1 2 Class<Son> sonClass = Son.class; 3 //2 4 Class<?> aClass = Maint.class.getClassLoader().loadClass("com.Son"); 5 //3 6 Class<?> bClass = Class.forName("com.Son");
程式碼2:
其實這種方法調運的是:ClassLoader.loadClass(name, false)方法
引數一:name,需要載入的類的名稱
引數二:false,這個類載入以後是否需要去連線(不需要linking)
程式碼:3:
其實這種方法調運的是:Class.forName(className, true, ClassLoader.getCallerClassLoader())方法
引數一:className,需要載入的類的名稱。
引數二:true,是否對class進行初始化(需要initialize)
引數三:classLoader,對應的類載入器
其中1、2都是將.class檔案載入到JVM中,得到Class物件,但是還沒有連線(Link),靜態程式碼塊不會執行;
程式碼3得到Class物件並且已經完成初始化<clinit>,靜態程式碼塊會執行。( Class.forName("com.mysql.jdbc.Driver");//通過這種方式將驅動註冊到驅動管理器上)。
1、2、3都不會執行物件的建構函式。
五、思考:<clinit>和<init>區別
<clinit>是類(Class)初始化執行的方法,<init>是物件初始化執行的方法(建構函式);