目錄:JVM總括:目錄html
類加載過程就是將.class文件轉化爲Class對象,類實例化的過程,(User user = new User(); 這個過程是對象實例化的過程);java
一個.class文件只有一個Class對象(字節碼對象),能夠有無數個對象(例如:new User(););mysql
一、Load:web
將編譯後的.class文件以二進制流的方式加載到JVM內存中,並轉化爲特定的數據結構,用到的就是classLoad二類加載器。這個過程當中校驗cafe babe魔法數、常量池、文件長度、是否有父類等。sql
其實加載階段用一句話來描述就是:把代碼數據加載到內存中。數據庫
二、Link:bootstrap
分爲驗證、準備、解析三步。windows
驗證:更爲詳細的驗證,好比:final是否規範(二次賦值不規範)、static是否合理(靜態方法必須引用靜態變量)、類型是否正確。數據結構
準備:當完成字節碼文件的校驗以後,JVM 便會開始爲類變量分配內存並初始化。這裏須要注意兩個關鍵點,即內存分配的對象以及初始化的類型。框架
內存分配的對象。Java 中的變量有「類變量」和「類成員變量」兩種類型,「類變量」指的是被 static 修飾的變量,而其餘全部類型的變量都屬於「類成員變量」。
在準備階段,JVM 只會爲「類變量」分配內存,而不會爲「類成員變量」分配內存。「類成員變量」的內存分配須要等到初始化階段纔開始。
好比下面的代碼在準備階段,只會爲 age 屬性分配內存,而不會爲 website 屬性分配內存。
public static int age= 3;
public String website = "www.beijingdesigner.com";
例以下面的代碼在準備階段以後,age的值將是 0,而不是 3。
public static int age= 3;
但若是一個變量是常量(被 static final 修飾)的話,那麼在準備階段,屬性便會被賦予用戶但願的值。例以下面的代碼在準備階段以後,number 的值將是 3,而不是 0。
public static final int number = 3;
這其中的道理咱們想下就明白了。
兩個語句的區別是一個有 final 關鍵字修飾,另一個沒有。被final修飾的常量,是不容許再被改變的。因此初始化就是3了。
解析:把類中的符號引用轉化爲直接引用,完成內存結構佈局。(符號引用轉化爲直接引用:例如:test1() { test2(); },這裏test1調用test2方法就是符號引用,但實際test2()經過一個指針指向test2方法的內存地址,這個指針負責調用,它就是直接引用)。
三、Init:
執行類構造器<clinit>,遞歸初始化父類的靜態代碼塊,不執行構造函數。(先執靜態變量,再執行行靜態代碼塊)。
任何小寫的class定義的類都有一個魔法屬性:class,
int j; Class<Integer> integerClass = int.class; Class<Integer> integerClass1 = Integer.class;
類加載是怎麼肯定類文件的位置呢?
一、Bootstrap:最高級的類加載器,裝置最核心的類,如:Object、System、String;
二、Extension ClassLoader:JDK9以前的類加載器,之後的爲Platform ClassLoader,加載系統的擴展類;
三、Application ClassLoader:應用類加載器,主要加載用戶自定義CLASSPATH路徑下的類。
類加載器並不是繼承關係,只是以組合的方式服用父加載器功能,符合優先原則。
其中第2、第三層爲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);
----------------------------------- sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@5acf9800 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
自定義類加載器的狀況:
一、隔離加載類:框架中吧類加載到不一樣的環境中
二、修改類加載方式:除了Bootstrap,其餘類並不是必定要加入。
三、擴展加載源:好比加載數據庫;
四、防止源碼泄露:能夠對類進行加密,那加載的時候須要自定義加載器對類進行解密加載。
自定義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; } }
調用,其中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()); }
從字節碼、執行步驟兩個方面分析
Object object = new Object();,經過javap -verbose -p查看對象建立的字節碼指令:
(1)new:若是找不到Class對象就進行類加載,而後分配內存(本類路徑上全部的屬性都分配),其中對象的引用也是個變量也佔內存(4個字節),這個指令執行完畢會把對象的壓入虛擬機棧頂。
(2)dup:在棧頂複雜引用,若是<init>有參數,把參數壓入操做棧,兩個引用,壓入棧底的用來賦值或保存到局部變量表中,棧頂引用做爲句柄調用相關方法。
(3)invokespecial:調用對象實例化方法,經過棧頂方法調用<init>方法(也就是調用構造方法)。
(1)確認類元信息是否存在:接到new指令時,在metaspace檢查類元信息是否存在,沒有就在雙親委派模式下進行類加載,生成Class對象。
(2)分配對象內存:首先計算對象佔用空間大小(成員變量是引用變量就分配4個字節大小的變量空間),在堆中劃份內存空間給新對象(分配空間須要進行同步操做,如:cas)。
(3)設定默認值:成員變量設置不一樣形式的0值;
(4)設置對象頭:設置對象的哈希碼、鎖信息、對象所屬的類元信息,設置取決於JVM。
(5)執行init方法:初始化成員變量,執行代碼塊,調用類的構造方法,把堆對象首地址賦值給引用變量。
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,對應的類加載器
其中一、2都是將.class文件加載到JVM中,獲得Class對象,可是尚未鏈接(Link),靜態代碼塊不會執行;
代碼3獲得Class對象而且已經完成初始化<clinit>,靜態代碼塊會執行。( Class.forName("com.mysql.jdbc.Driver");//經過這種方式將驅動註冊到驅動管理器上)。
一、二、3都不會執行對象的構造函數。
<clinit>是類(Class)初始化執行的方法,<init>是對象初始化執行的方法(構造函數);