20、java中的类加载机制
1、類加載機制是什么?
?????? 類加載機制指的就是jvm將類的信息動態添加到內存并使用的一種機制。
2、那么類加載的具體流程是什么呢?
??????? 一般說類加載只有三步:加載、連接和初始化,其中連接包括驗證、準備和解析,用于將運行時加載的類文件添加到jre環境中使用。
????????????????????????????? ?
?
加載:加載就是將類文件讀取到內存中并對類信息做了初步的處理。這里是有講究的,jvm一啟動就會先加載一些類,這些類位于jdk中的rt.jar中,而另外的類就不會加載,而是當用到時才加載,這也就是預加載和運行時加載,運行時加載時jvm會先去內存中查看是否存在要使用的類,如果沒有則按著這個類的全限定名進行加載,之前在IO部分也說過,像這種文件,在內存中是以一種數據序列的形式存在,也就是加載到內存的是類文件的二進制流信息,另外就是這個二進制流可以通過多條路徑獲取,不一定非要讀取磁盤上的文件信息。然后jvm不會閑著,他會將類中的靜態信息如:類信息、靜態變量、常量等添加到jvm中的方法區,一般還會在堆中創建一個Class的對象用來表示這個類的信息。
驗證:對加載到內存的二進制流的信息進行合法性校驗,一切為了安全。試想一下,如果叮咣叮咣的寫一堆三字經到.class文件,然后加載到內存,這時候jvm一運行不崩潰才怪。
準備:這個階段主要是對類變量進行操作,如果僅是類變量,那么會根據其類型在方法區給變量開辟空間,并附一個對應類型的默認值。如果變量除了使用static修飾外,還用final修飾,那么則給變量賦定義的值。
解析:其實這一個我也不是太理解,解析是虛擬機將常量池內的符號引用解析為直接引用。以下為自己理解,大白話描述:何為符號引用,我理解的就是使用源碼中的類名、方法名、變量名等來應用內存中對應的地址域所對應的數據,而直接引用呢就是將這些名稱進行解析成一個可以直接指向內存地址的變量,直接引用的數據一定在內存中已經存在了,而符號引用所對應的數據不一定存在,我自認為的就是只是聲明了一個變量,沒有賦值,所以有的時候他自己都不知道自己到底指向哪里。
初始化:就是根據類中的構造方法來初始化類的過程,例如:我們都知道靜態變量是隨著類的加載而加載的,所以這里就會給靜態變量初始化一個值,而對于那些成員方法,如果在構造器中沒有調用的話,他們是不會被調用的。說的通俗易懂就是初始化那些被static修飾的部分,因為隨著類的加載而加載。
3、加載類的流程具體是誰實現的?
?????? 上面一直說jvm加載類,那么如果再往細處劃分又是怎么樣的呢?其實jvm中有專門管著加載類的工具,這就是類加載器。類加載器主要用于根據類的全限定名將對應的class文件的流信息加載到虛擬機內存,并將其轉為Class對象。類加載器有四種:
?????? 啟動類加載器(Bootstrap ClassLoader):加載\Java\jdk1.8\jre\lib 下的類
?????? 擴展類加載器(Extension ClassLoader):加載\Java\jdk1.8\jre\lib\ext下的類
?????? 應用程序類加載器(Application ClassLoader):加載用戶路徑上的類,就是開發者自定義的類
?????? 自定義加載器:加載自定義位置的類文件,為了避免以上三個加載器都加載不到指定的包。
類加載器的主要作用是根據類的全類名將類的字節碼信息添加到內存中并根據字節碼信息創建一個Class對象來表示這個類信息,這里充分體現了面向對象的思想,萬物皆可為對象,每一個不同類的信息的就是類的一個對象。
類加載器的基本用法:
public static void main(String[] args) throws ClassNotFoundException {// 返回系統類加載器,平時使用不多,常用當前類的加載器去獲取指定的類信息//ClassLoader classLoader = ClassLoader.getSystemClassLoader();// 常用當前類的加載器去獲取指定的類信息ClassLoader classLoader = SelfClassLoaderTest.class.getClassLoader();// 獲取當前類的類加載器的父類加載器ClassLoader parentClassLoader = classLoader.getParent();// 根據類的全路徑名獲取類信息,創建一個 Class 的對象Class loadClass = classLoader.loadClass("com.czp.reflection.Student");// 加載一些配置文件可以使用這個方法InputStream inputStream = ClassLoader.getSystemResourceAsStream("文件名"); }類加載器加載類時的源碼分析:
//根據name查詢對應類的Class對象,最終通過jni技術用的c、c++的方法 protected final Class<?> findLoadedClass(String name) {if (!checkName(name))return null;return findLoadedClass0(name); } private native final Class<?> findLoadedClass0(String name);//這個方法應該被自定義的類加載器覆蓋 protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name); }//加載的機制是雙親委托加載 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {//先鎖住synchronized (getClassLoadingLock(name)) {// 檢查類是否已經加載Class<?> c = findLoadedClass(name);//如果沒有加載if (c == null) {try {//判斷是否有父類的加載器if (parent != null) {//有父類加載器就用父類加載器去加載c = parent.loadClass(name, false);} else {//沒有父類加載器了,則就用啟動類加載器去加載c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) { }//如果沒有加載到對應的類信息if (c == null) {// findClass方法是一個空方法,所以這里如果使用的是自定義加載器加載類的話,就會試圖通過自定義的加載器去獲取對應的類信息c = findClass(name);// 定義類裝入器;記錄數據sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;} }//將字節數組轉換為類類的實例 protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {protectionDomain = preDefineClass(name, protectionDomain);String source = defineClassSourceLocation(protectionDomain);Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);postDefineClass(c, protectionDomain);return c; }如何實現一個自定義類加載器?思路呢就是先讀取到.class文件,然后根據讀到的數據創建一個Class的對象,正好ClassLoader中已經幫我們實現了,只是需要自定義查找文件的路徑,獲取到文件的字節流,然后根據提供的defineClass方法將字節轉成對應Class對象即可。需要繼承ClassLoader類,并重寫findClass方法,實現代碼如下:
public class MyClassLoader extends ClassLoader{private String path;//在哪個目錄下讀取.class文件public MyClassLoader(String path) {this.path = path;}@SuppressWarnings("deprecation")@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte []b = getClassBinaryData(name);if (b != null) {return defineClass(b, 0, b.length);//將字節數組轉陳對應的Class對象} else {throw new ClassNotFoundException();}}//根據路徑獲取到對應的字節數組private byte[] getClassBinaryData(String className) {String classPath = path + "/" + className.replace(".", "/") +".class";byte[] bs = null;FileInputStream fis = null;ByteArrayOutputStream baos = null;try {fis = new FileInputStream(classPath);baos = new ByteArrayOutputStream();byte[] buffer = new byte[2048];int num = 0;while ((num = fis.read(buffer)) != -1) {baos.write(buffer, 0, num);}bs = baos.toByteArray();} catch (IOException e) {e.printStackTrace();} finally {try {if (fis!=null) {fis.close();}} catch (Exception e2) {}}return bs;} }-----------------------------測試public static void main(String[] args) {MyClassLoader myClassLoader = new MyClassLoader("E:/");try {Class<?> findClass = myClassLoader.findClass("EnumTest");System.out.println(findClass);} catch (ClassNotFoundException e) {e.printStackTrace();} }? ? ?? 這就是類加載器的介紹,這個時候不知是否會有疑問,如果兩個加載器要加載的類在兩個加載器的路徑下均有資源,這要怎么辦呢?是兩個都添加,然后覆蓋,還是咋滴?其實類加載器加載類文件時遵循一種雙親委派模型機制,介紹如下:當一個類加載器收到類加載的請求時,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當父加載器在自己的搜索范圍內找不到指定的類時(即ClassNotFoundException),子加載器才會嘗試自己去加載,這幾個加載器之間的父子關系為:啟動類加載器>擴展類加載器>應用程序類加載器>自定義加載器,這里的父子關系并不指的是類上邊的extends。
總結
以上是生活随笔為你收集整理的20、java中的类加载机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑配置正在更新可以取消吗(电脑配置正在
- 下一篇: 21、java中的反射机制