免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
JVM自定義類加載器加載指定classPath下的所有class及jar

一、JVM中的類加載器類型
  從Java虛擬機(jī)的角度講,只有兩種不同的類加載器:啟動(dòng)類加載器和其他類加載器。
  1.啟動(dòng)類加載器(Boostrap ClassLoader):這個(gè)是由c 實(shí)現(xiàn)的,主要負(fù)責(zé)JAVA_HOME/lib目錄下的核心 api 或 -Xbootclasspath 選項(xiàng)指定的jar包裝入工作。
  2.其他類加載器:由java實(shí)現(xiàn),可以在方法區(qū)找到其Class對象。這里又細(xì)分為幾個(gè)加載器
    a).擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)用于加載JAVA_HOME/lib/ext目錄中的,或者被-Djava.ext.dirs系統(tǒng)變量指定所指定的路徑中所有類庫(jar),開發(fā)者可以直接使用擴(kuò)展類加載器。java.ext.dirs系統(tǒng)變量所指定的路徑的可以通過System.getProperty("java.ext.dirs")來查看。
    b).應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)java -classpath或-Djava.class.path所指的目錄下的類與jar包裝入工作。開發(fā)者可以直接使用這個(gè)類加載器。在沒有指定自定義類加載器的情況下,這就是程序的默認(rèn)加載器。
    c).自定義類加載器(User ClassLoader):在程序運(yùn)行期間, 通過java.lang.ClassLoader的子類動(dòng)態(tài)加載class文件, 體現(xiàn)java動(dòng)態(tài)實(shí)時(shí)類裝入特性。

  這四個(gè)類加載器的層級(jí)關(guān)系,如下圖所示。

      

二、為什么要自定義類加載器
區(qū)分同名的類:假定在tomcat 應(yīng)用服務(wù)器,上面部署著許多獨(dú)立的應(yīng)用,同時(shí)他們擁有許多同名卻不同版本的類。要區(qū)分不同版本的類當(dāng)然是需要每個(gè)應(yīng)用都擁有自己獨(dú)立的類加載器了,否則無法區(qū)分使用的具體是哪一個(gè)。
類庫共享:每個(gè)web應(yīng)用在tomcat中都可以使用自己版本的jar。但存在如Servlet-api.jar,java原生的包和自定義添加的Java類庫可以相互共享。
加強(qiáng)類:類加載器可以在 loadClass 時(shí)對 class 進(jìn)行重寫和覆蓋,在此期間就可以對類進(jìn)行功能性的增強(qiáng)。比如使用javassist對class進(jìn)行功能添加和修改,或者添加面向切面編程時(shí)用到的動(dòng)態(tài)代理,以及 debug 等原理。
熱替換:在應(yīng)用正在運(yùn)行的時(shí)候升級(jí)軟件,不需要重新啟動(dòng)應(yīng)用。比如toccat服務(wù)器中JSP更新替換。

三、自定義類加載器
  3.1 ClassLoader實(shí)現(xiàn)自定義類加載器相關(guān)方法說明
    要實(shí)現(xiàn)自定義類加載器需要先繼承ClassLoader,ClassLoader類是一個(gè)抽象類,負(fù)責(zé)加載classes的對象。自定義ClassLoader中至少需要了解其中的三個(gè)的方法: loadClass,findClass,defineClass。
   

public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
    loadClass:JVM在加載類的時(shí)候,都是通過ClassLoader的loadClass()方法來加載class的,loadClass使用雙親委派模式。如果要改變雙親委派模式,可以修改loadClass來改變class的加載方式。雙親委派模式這里就不贅述了。
    findClass:ClassLoader通過findClass()方法來加載類。自定義類加載器實(shí)現(xiàn)這個(gè)方法來加載需要的類,比如指定路徑下的文件,字節(jié)流等。
    definedClass:definedClass在findClass中使用,通過調(diào)用傳進(jìn)去一個(gè)Class文件的字節(jié)數(shù)組,就可以方法區(qū)生成一個(gè)Class對象,也就是findClass實(shí)現(xiàn)了類加載的功能了。

    貼上一段ClassLoader中l(wèi)oadClass源碼,見見真面目...
   

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) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

        if (c == null) {            // If still not found, then invoke findClass in order            // to find the class.            long t1 = System.nanoTime();            c = findClass(name);            // this is the defining class loader; record the stats            sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);            sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);            sun.misc.PerfCounter.getFindClasses().increment();        }    }    if (resolve) {        resolveClass(c);    }    return c;}

}

    源碼說明...

/**

  • Loads the class with the specified <a href="#name">binary name</a>. The
  • default implementation of this method searches for classes in the
  • following order:
  • <ol>
  • <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
  • has already been loaded. </p></li>
  • <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
  • on the parent class loader. If the parent is <tt>null</tt> the class
  • loader built-in to the virtual machine is used, instead. </p></li>
  • <li><p> Invoke the {@link #findClass(String)} method to find the
  • class. </p></li>
  • </ol>
  • <p> If the class was found using the above steps, and the
  • <tt>resolve</tt> flag is true, this method will then invoke the {@link
  • #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
  • <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
  • #findClass(String)}, rather than this method. </p>
  • <p> Unless overridden, this method synchronizes on the result of
  • {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
  • during the entire class loading process.
  • @param name
  • The <a href="#name">binary name</a> of the class
  • @param resolve
  • If <tt>true</tt> then resolve the class
  • @return The resulting <tt>Class</tt> object
  • @throws ClassNotFoundException
  • If the class could not be found
    */

   翻譯過來大概是:使用指定的二進(jìn)制名稱來加載類,這個(gè)方法的默認(rèn)實(shí)現(xiàn)按照以下順序查找類: 調(diào)用findLoadedClass(String)方法檢查這個(gè)類是否被加載過 使用父加載器調(diào)用loadClass(String)方法,如果父加載器為Null,類加載器裝載虛擬機(jī)內(nèi)置的加載器調(diào)用findClass(String)方法裝載類, 如果,按照以上的步驟成功的找到對應(yīng)的類,并且該方法接收的resolve參數(shù)的值為true,那么就調(diào)用resolveClass(Class)方法來處理類。 ClassLoader的子類最好覆蓋findClass(String)而不是這個(gè)方法。 除非被重寫,這個(gè)方法默認(rèn)在整個(gè)裝載過程中都是同步的(線程安全的)。

   resolveClass:Class載入必須鏈接(link),鏈接指的是把單一的Class加入到有繼承關(guān)系的類樹中。這個(gè)方法給Classloader用來鏈接一個(gè)類,如果這個(gè)類已經(jīng)被鏈接過了,那么這個(gè)方法只做一個(gè)簡單的返回。否則,這個(gè)類將被按照 Java?規(guī)范中的Execution描述進(jìn)行鏈接。

 3.2 自定義類加載器實(shí)現(xiàn)
    按照3.1的說明,繼承ClassLoader后重寫了findClass方法加載指定路徑上的class。先貼上自定義類加載器。

package com.chenerzhu.learning.classloader;

import java.nio.file.Files;
import java.nio.file.Paths;

/**

  • @author chenerzhu
  • @create 2018-10-04 10:47
    **/
    public class MyClassLoader extends ClassLoader {
    private String path;

    public MyClassLoader(String path) {
    this.path = path;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    try {
    byte[] result = getClass(name);
    if (result == null) {
    throw new ClassNotFoundException();
    } else {
    return defineClass(name, result, 0, result.length);
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }

    private byte[] getClass(String name) {
    try {
    return Files.readAllBytes(Paths.get(path));
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }

    

    以上就是自定義的類加載器了,實(shí)現(xiàn)的功能是加載指定路徑的class。再看看如何使用?!?/p>

package com.chenerzhu.learning.classloader;

import org.junit.Test;

/**

    首先通過構(gòu)造方法創(chuàng)建MyClassLoader對象myClassLoader,指定加載src/test/resources/bean/Hello.class路徑的Hello.class(當(dāng)然這里只是個(gè)例子,直接指定一個(gè)class的路徑了)。然后通過myClassLoader方法loadClass加載Hello的Class對象,最后實(shí)例化對象。以下是輸出結(jié)果,看得出來實(shí)例化成功了,并且類加載器使用的是MyClassLoader。

com.chenerzhu.learning.classloader.bean.Hello@2b2948e2
com.chenerzhu.learning.classloader.MyClassLoader@335eadca

四、類Class卸載
  JVM中class和Meta信息存放在PermGen space區(qū)域(JDK1.8之后存放在MateSpace中)。如果加載的class文件很多,那么可能導(dǎo)致元數(shù)據(jù)空間溢出。引起java.lang.OutOfMemory異常。對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。所以需要在JVM中卸載(unload)類Class。
  JVM中的Class只有滿足以下三個(gè)條件,才能被GC回收,也就是該Class被卸載(unload):

該類所有的實(shí)例都已經(jīng)被GC。
該類的java.lang.Class對象沒有在任何地方被引用。
加載該類的ClassLoader實(shí)例已經(jīng)被GC。
  很容易理解,就是要被卸載的類的ClassLoader實(shí)例已經(jīng)被GC并且本身不存在任何相關(guān)的引用就可以被卸載了,也就是JVM清除了類在方法區(qū)內(nèi)的二進(jìn)制數(shù)據(jù)。
  JVM自帶的類加載器所加載的類,在虛擬機(jī)的生命周期中,會(huì)始終引用這些類加載器,而這些類加載器則會(huì)始終引用它們所加載的類的Class對象。因此這些Class對象始終是可觸及的,不會(huì)被卸載。而用戶自定義的類加載器加載的類是可以被卸載的。雖然滿足以上三個(gè)條件Class可以被卸載,但是GC的時(shí)機(jī)我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。

五、JVM自定義類加載器加載指定classPath下的所有class及jar
  經(jīng)過以上幾個(gè)點(diǎn)的說明,現(xiàn)在可以實(shí)現(xiàn)JVM自定義類加載器加載指定classPath下的所有class及jar了。這里沒有限制class和jar的位置,只要是classPath路徑下的都會(huì)被加載進(jìn)JVM,而一些web應(yīng)用服務(wù)器加載是有限定的,比如tomcat加載的是每個(gè)應(yīng)用classPath “/classes”加載class,classPath “/lib”加載jar。以下就是代碼啦...

  

package com.chenerzhu.learning.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**

  • @author chenerzhu
  • @create 2018-10-04 12:24
    **/
    public class ClassPathClassLoader extends ClassLoader{

    private static Map<String, byte[]> classMap = new ConcurrentHashMap<>();
    private String classPath;

    public ClassPathClassLoader() {
    }

    public ClassPathClassLoader(String classPath) {
    if (classPath.endsWith(File.separator)) {
    this.classPath = classPath;
    } else {
    this.classPath = classPath File.separator;
    }
    preReadClassFile();
    preReadJarFile();
    }

    public static boolean addClass(String className, byte[] byteCode) {
    if (!classMap.containsKey(className)) {
    classMap.put(className, byteCode);
    return true;
    }
    return false;
    }

    /**

    • 這里僅僅卸載了myclassLoader的classMap中的class,虛擬機(jī)中的
    • Class的卸載是不可控的
    • 自定義類的卸載需要MyClassLoader不存在引用等條件
    • @param className
    • @return
      */
      public static boolean unloadClass(String className) {
      if (classMap.containsKey(className)) {
      classMap.remove(className);
      return true;
      }
      return false;
      }

    /**

    private byte[] getClass(String className) {
    if (classMap.containsKey(className)) {
    return classMap.get(className);
    } else {
    return null;
    }
    }

    private void preReadClassFile() {
    File[] files = new File(classPath).listFiles();
    if (files != null) {
    for (File file : files) {
    scanClassFile(file);
    }
    }
    }

    private void scanClassFile(File file) {
    if (file.exists()) {
    if (file.isFile() && file.getName().endsWith(".class")) {
    try {
    byte[] byteCode = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
    String className = file.getAbsolutePath().replace(classPath, "")
    .replace(File.separator, ".")
    .replace(".class", "");
    addClass(className, byteCode);
    } catch (IOException e) {
    e.printStackTrace();
    }
    } else if (file.isDirectory()) {
    for (File f : file.listFiles()) {
    scanClassFile(f);
    }
    }
    }
    }

    private void preReadJarFile() {
    File[] files = new File(classPath).listFiles();
    if (files != null) {
    for (File file : files) {
    scanJarFile(file);
    }
    }
    }

    private void readJAR(JarFile jar) throws IOException {
    Enumeration<JarEntry> en = jar.entries();
    while (en.hasMoreElements()) {
    JarEntry je = en.nextElement();
    je.getName();
    String name = je.getName();
    if (name.endsWith(".class")) {
    //String className = name.replace(File.separator, ".").replace(".class", "");
    String className = name.replace("\", ".")
    .replace("/", ".")
    .replace(".class", "");
    InputStream input = null;
    ByteArrayOutputStream baos = null;
    try {
    input = jar.getInputStream(je);
    baos = new ByteArrayOutputStream();
    int bufferSize = 1024;
    byte[] buffer = new byte[bufferSize];
    int bytesNumRead = 0;
    while ((bytesNumRead = input.read(buffer)) != -1) {
    baos.write(buffer, 0, bytesNumRead);
    }
    addClass(className, baos.toByteArray());
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    if (baos != null) {
    baos.close();
    }
    if (input != null) {
    input.close();
    }
    }
    }
    }
    }

    private void scanJarFile(File file) {
    if (file.exists()) {
    if (file.isFile() && file.getName().endsWith(".jar")) {
    try {
    readJAR(new JarFile(file));
    } catch (IOException e) {
    e.printStackTrace();
    }
    } else if (file.isDirectory()) {
    for (File f : file.listFiles()) {
    scanJarFile(f);
    }
    }
    }
    }

    public void addJar(String jarPath) throws IOException {
    File file = new File(jarPath);
    if (file.exists()) {
    JarFile jar = new JarFile(file);
    readJAR(jar);
    }
    }
    } 

  如何使用的代碼就不貼了,和3.2節(jié)自定義類加載器的使用方式一樣。只是構(gòu)造方法的參數(shù)變成classPath了,篇末有代碼。當(dāng)創(chuàng)建MyClassLoader對象時(shí),會(huì)自動(dòng)添加指定classPath下面的所有class和jar里面的class到classMap中,classMap維護(hù)className和classCode字節(jié)碼的關(guān)系,只是個(gè)緩沖作用,避免每次都從文件中讀取。自定義類加載器每次loadClass都會(huì)首先在JVM中找是否已經(jīng)加載className的類,如果不存在就會(huì)到classMap中取,如果取不到就是加載錯(cuò)誤了。

六、最后
  至此,JVM自定義類加載器加載指定classPath下的所有class及jar已經(jīng)完成了。這篇博文花了兩天才寫完,在寫的過程中有意識(shí)地去了解了許多代碼的細(xì)節(jié),收獲也很多。本來最近僅僅是想實(shí)現(xiàn)Quartz控制臺(tái)頁面任務(wù)添加支持動(dòng)態(tài)class,結(jié)果不知不覺跑到類加載器的坑了,在此也趁這個(gè)機(jī)會(huì)總結(jié)一遍。當(dāng)然以上內(nèi)容并不能保證正確,所以希望大家看到錯(cuò)誤能夠指出,幫助我更正已有的認(rèn)知,共同進(jìn)步。。。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
類加載機(jī)制(類加載過程和類加載器)
一看你就懂,Java中的ClassLoader詳解
快速了解類加載采用雙親委托機(jī)制的原因與實(shí)現(xiàn)
JVM真香系列:輕松理解class文件到虛擬機(jī)(下)
Java虛擬機(jī)學(xué)習(xí)(10):類加載器(ClassLoader)
JVM類加載機(jī)制詳解(二)類加載器與雙親委派模型
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服