摘要:在沒有指定自定義類加載器的情況下,這就是程序的默認(rèn)加載器。自定義類加載器雙親委派模型避免由于字節(jié)碼被多次加載。首先自定義類加載器,最重要的就是先繼承這個類。
一. 類加載器
JVM中的類加載器:在jvm中,存在兩種類加載器,
a) Boostrap ClassLoader:這個是由c++實現(xiàn)的,所以在方法區(qū)并沒有Class對象的實例存在。用于加載JAVA_HOME/bin目錄下的jar包
b) 其他類加載器:由java實現(xiàn),可以在方法區(qū)找到其Class對象。這里又細分為幾個加載器
擴展類加載器(Extension ClassLoader):它負(fù)責(zé)用于加載JAVA_HOME/lib/ext目錄中的,或者被java.ext.dirs系統(tǒng)變量指定所指定的路徑中所有類庫,開發(fā)者可以直接使用擴展類加載器。java.ext.dirs系統(tǒng)變量所指定的路徑的可以通過程序來查看。System.getProperty("java.ext.dirs")
應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)加載用戶類路徑上指定的類庫。開發(fā)者可以直接使用這個類加載器。ps:在沒有指定自定義類加載器的情況下,這就是程序的默認(rèn)加載器。
自定義類加載器(User ClassLoader):
雙親委派模型:避免由于Class字節(jié)碼被多次加載。底層類加載器在收到一個類加載的請求的時候,都先把請求轉(zhuǎn)發(fā)給其父加載器(并不是一個繼承的關(guān)系),父類查找不到才會讓子類去加載。如果強制只有雙親委派模型,那么,web服務(wù)器的隔離是無法實現(xiàn)的。
??由于最近想做一個類似tomcat一樣的簡易版web服務(wù)器來加深理解http請求的處理過程。我們都清楚,每個web應(yīng)用在tomcat中都可以使用自己版本的jar。除了少量的包,如Servlet-api.jar,還有一些java原生的包之外,tomcat是會為每個不同的應(yīng)用加載不同的jar包或者class,且彼此之間不會相互影響。這一步是通過自定義類加載器來實現(xiàn)的,在虛擬機層面,判斷兩個Class是否相等的前提是他們是同一個類加載器加載的,否則就沒有意義了。這篇文章簡單的實現(xiàn)一個自定義的加載過程,PS:這個例子并沒有破壞雙親委派模型,因為例子中依然會查找父類,如果找不到再使用子類加載。接下來筆者會再更新破壞雙親委派模型的博客,這里挖個坑。
??首先自定義類加載器,最重要的就是先繼承ClassLoader這個類。加載器的加載流程是,給出一個Class文件的全限定名,然后調(diào)用loadClass方法,這個方法每部會現(xiàn)在自己已經(jīng)加載的類中查找,如果找到就返回。找不到則向父類查找,如果父類都找不到這才開始自己加載,調(diào)用findClass方法。所以我們只要覆蓋findClass方法就可以實現(xiàn)自己定義的加載了。順帶一提,ClassLoader中有一個方法叫defineClass(String, byte[], int, int);這個方法通過傳進去一個Class文件的字節(jié)數(shù)組,就可以方法區(qū)生成一個Class對象。所以要實現(xiàn)findClass的目標(biāo)就很明確了,只要將Class文件讀取進來,然后生成byte數(shù)組,調(diào)用defineClass方法就可以了。
@Override protected Class> findClass(String name){ try { byte[] result = getClassFromFileOrMap(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; }
??那么如何找到Class文件呢?手動生成/網(wǎng)絡(luò)下載我們就暫時不談?wù)摗_@里只說兩種最常見的,一是直接.class文件中查找,二是從jar包中加載。
從class文件中加載非常簡單。只要找到相應(yīng)的文件,就可以通過字節(jié)流讀取進來。代碼如下:
input = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray();
??從jar讀取則相對麻煩一點,java給我們提供了一個專門用來讀取jar包文件的類,抽象成一個JarFile的對象。通過調(diào)用這個對象的getInputStream方法,也是可以獲取文件的輸入流,從而讀取字節(jié)數(shù)組。筆者做了一點相應(yīng)的緩存,如果每次查找文件都要先讀取jar文件,再遍歷查找class文件是非常耗時的操作。于是,筆者選擇再加載之前,把所有的jar包中的所有class讀取到內(nèi)存中,保存在一個map對象中。建立一個全限定名和字節(jié)數(shù)組的映射。這樣在加載階段,就能省下很多的時間了。全部的代碼如下
package com.chasel.cloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * 自定義的類加載器【子類優(yōu)先】 * @author hujiancai * @description * @data 2017年3月11日 * @version v_0.1 */ public class MyWebAppLoader extends ClassLoader{ /** * lib:表示加載的文件在jar包中 * 類似tomcat就是{PROJECT}/WEB-INF/lib/ */ private String lib; /** * classes:表示加載的文件是單純的class文件 * 類似tomcat就是{PROJECT}/WEB-INF/classes/ */ private String classes; /** * 采取將所有的jar包中的class讀取到內(nèi)存中 * 然后如果需要讀取的時候,再從map中查找 */ private Mapmap; /** * 只需要指定項目路徑就好 * 默認(rèn)jar加載路徑是目錄下{PROJECT}/WEB-INF/lib/ * 默認(rèn)class加載路徑是目錄下{PROJECT}/WEB-INF/classes/ * @param webPath * @throws MalformedURLException * @throws SecurityException * @throws NoSuchMethodException */ public MyWebAppLoader(String webPath) throws NoSuchMethodException, SecurityException, MalformedURLException{ lib = webPath + "WEB-INF/lib/"; classes = webPath + "WEB-INF/classes/"; map = new HashMap (64); preReadJarFile(); } /** * 按照父類的機制,如果在父類中沒有找到的類 * 才會調(diào)用這個findClass來加載 * 這樣只會加載放在自己目錄下的文件 * 而系統(tǒng)自帶需要的class并不是由這個加載 */ @Override protected Class> findClass(String name){ try { byte[] result = getClassFromFileOrMap(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; } /** * 從指定的classes文件夾下找到文件 * @param name * @return */ private byte[] getClassFromFileOrMap(String name){ String classPath = classes + name.replace(".", File.separatorChar) + ".class"; File file = new File(classPath); if(file.exists()){ InputStream input = null; try { input = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(input != null){ try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } }else{ if(map.containsKey(name)) { //去除map中的引用,避免GC無法回收無用的class文件 return map.remove(name); } } return null; } /** * 預(yù)讀lib下面的包 */ private void preReadJarFile(){ List list = scanDir(); for(File f : list){ JarFile jar; try { jar = new JarFile(f); readJAR(jar); } catch (IOException e) { e.printStackTrace(); } } } /** * 讀取一個jar包內(nèi)的class文件,并存在當(dāng)前加載器的map中 * @param jar * @throws IOException */ private void readJAR(JarFile jar) throws IOException{ Enumeration en = jar.entries(); while (en.hasMoreElements()){ JarEntry je = en.nextElement(); String name = je.getName(); if (name.endsWith(".class")){ String clss = name.replace(".class", "").replaceAll("/", "."); if(this.findLoadedClass(clss) != null) continue; InputStream input = jar.getInputStream(je); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } byte[] cc = baos.toByteArray(); input.close(); map.put(clss, cc);//暫時保存下來 } } } /** * 掃描lib下面的所有jar包 * @return */ private List scanDir() { List list = new ArrayList (); File[] files = new File(lib).listFiles(); for (File f : files) { if (f.isFile() && f.getName().endsWith(".jar")) list.add(f); } return list; } /** * 添加一個jar包到加載器中去。 * @param jarPath * @throws IOException */ public void addJar(String jarPath) throws IOException{ File file = new File(jarPath); if(file.exists()){ JarFile jar = new JarFile(file); readJAR(jar); } } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/66798.html
任何程序都需要加載到內(nèi)存才能與CPU進行交流 同理, 字節(jié)碼.class文件同樣需要加載到內(nèi)存中,才可以實例化類 ClassLoader的使命就是提前加載.class 類文件到內(nèi)存中 在加載類時,使用的是Parents Delegation Model(溯源委派加載模型) Java的類加載器是一個運行時核心基礎(chǔ)設(shè)施模塊,主要是在啟動之初進行類的加載、鏈接、初始化 showImg(https://s...
摘要:作用負(fù)責(zé)將加載到中審查每個類由誰加載父優(yōu)先的等級加載機制將字節(jié)碼重新解析成統(tǒng)一要求的對象格式類結(jié)構(gòu)分析為了更好的理解類的加載機制,我們來深入研究一下和他的方法。就算兩個是同一份字節(jié)碼,如果被兩個不同的實例所加載,也會認(rèn)為它們是兩個不同。 申明:本文首發(fā)于 詳細深入分析 ClassLoader 工作機制 ,如有轉(zhuǎn)載,注明原出處即可,謝謝配合。 什么是 ClassLoader ? 大家...
摘要:當(dāng)程序使用某個類時,如果該類還沒被初始化,加載到內(nèi)存中,則系統(tǒng)會通過加載連接初始化三個過程來對該類進行初始化。一旦一個類被加載到中之后,就不會再次載入了。它既可以從本地文件系統(tǒng)獲取二進制文件來加載類,也可以遠程主機獲取二進制文件來加載類。 當(dāng)程序使用某個類時,如果該類還沒被初始化,加載到內(nèi)存中,則系統(tǒng)會通過加載、連接、初始化三個過程來對該類進行初始化。該過程就被稱為類的初始化 類加載 ...
摘要:,關(guān)閉不當(dāng)編譯器警告信息。創(chuàng)建固定大小的線程池。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)或者說能夠創(chuàng)建的最大線程大小。此線程池支持定時以及周期性執(zhí)行任務(wù)的需求。 目前在搞 Node.js,曾經(jīng)的 JAVA 知識忘了好多,為此整理了下,感嘆下工業(yè)語言還是有相當(dāng)?shù)膬?yōu)勢的。 流 Java所有的流類位于java.io包中,都分別繼承字以下四種抽象流類型。 Type 字節(jié)...
摘要:驗證驗證是連接階段的第一步,這一階段的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求,并且不會危害虛擬機自身的安全。字節(jié)碼驗證通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的符合邏輯的。 看過這篇文章,大廠面試你「雙親委派模型」,硬氣的說一句,你怕啥? 讀該文章姿勢 打開手頭的 IDE,按照文章內(nèi)容及思路進行代碼跟蹤與思考 手頭沒有 IDE,先收藏,回頭看 (萬一哪次面試問...
閱讀 1849·2021-09-29 09:35
閱讀 2711·2021-09-22 15:25
閱讀 1972·2021-08-23 09:43
閱讀 2049·2019-08-30 15:54
閱讀 3349·2019-08-30 15:53
閱讀 2387·2019-08-30 13:50
閱讀 2399·2019-08-30 11:24
閱讀 2269·2019-08-29 15:37