一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - JAVA提高第七篇 類加載器解析

JAVA提高第七篇 類加載器解析

2021-01-28 12:29pony1223 Java教程

這篇文章主要為大家詳細介紹了JAVA提高第七篇類加載器的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下

今天我們學習類加載器,關于類加載器其實和jvm有很大關系,在這里這篇文章只是簡單的介紹下類加載器,后面學習到jvm的時候還會詳細講到類加載器,本文分為下面幾個小節講解:

一、認識類加載器

1.什么是類加載器?

所謂的類加載器可以從其作用來理解,其功能就是將classpath目錄下.class文件,加載到內存中來進行一些處理,處理完的結果就是一些字節碼.那是誰把這些class類加載到內存中來的呢?就是類加載器。

2.jvm中默認的類加載器有哪些?

java虛擬機中可以安裝多個類加載器,系統默認三個主要的類加載器,每個類加載器負責加載不同位置的類:bootstrap,extclassloader,appclassloader

注意的是:

1.類加載器本身也是一個java類,因為類加載器本身也是一個java類,那么這個特殊的java類【類加載器】是有誰加載進來的呢?這顯然要有第一個類加載器,這第一個類加載器不是一個java類,它是bootstrap。

2.bootstrap不是一個java類,不需要類加載器java加載,他是嵌套在java虛擬機內核里面的。java 虛擬機內核已啟動的時候,他就已經在那里面了,他是用c++語言寫的一段二進制代碼。他可以去加載別的類,其中別的類就包含了類加載器【如上面提到的ext  和 app】。

案例:

下面我們寫個例子來獲取classloadertest這個類的類加載器的名字,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package study.javaenhance;
 
import java.util.arraylist;
 
public class classloadertest
{
  public static void main(string[] args) throws exception
  {
    //獲取類加載器,那么這個獲取的是一個實例對象,我們知道類加載器也有很多種,那么因此也有其對應的類存在,因此可以獲取到對應的字節碼
    system.out.println(classloadertest.class.getclassloader());
    //獲取類加載的字節碼,然后獲取到類加載字節碼的名字
    system.out.println(classloadertest.class.getclassloader().getclass().getname());
    //下面我們看下獲取非我們定義的類,比如system arraylist 等常用類
    system.out.println(system.class.getclassloader());
    system.out.println(arraylist.class.getclassloader());
    
    
  }
 
}

結果如下:

sun.misc.launcher$appclassloader@1c78e57
sun.misc.launcher$appclassloader
null
null

結果分析:

classloadertest的類加載器的名稱是appclassloader。也就是這個類是由appclassloader這個類加載器加載的。
system/arraylist的類加載器是null。這說明這個類加載器是由bootstrap加載的。因為我們上面說了bootstrap不是java類,不需要類加載器加載。所以他的類加載器是null。
==================================
我們說了java給我們提供了三種類加載器:bootstrap,extclassloader,appclassloader。這三種類加載器是有父子關系組成了一個樹形結構。bootstrap是根節點,bootstrap下面掛著extclassloader,extclassloader下面掛著appclassloader.

代碼演示如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package study.javaenhance;
 
import java.util.arraylist;
 
public class classloadertest
{
  public static void main(string[] args) throws exception
  {
    //獲取類加載器,那么這個獲取的是一個實例對象,我們知道類加載器也有很多種,那么因此也有其對應的類存在,因此可以獲取到對應的字節碼
    system.out.println(classloadertest.class.getclassloader());
    //獲取類加載的字節碼,然后獲取到類加載字節碼的名字
    system.out.println(classloadertest.class.getclassloader().getclass().getname());
    //下面我們看下獲取非我們定義的類,比如system arraylist 等常用類
    system.out.println(system.class.getclassloader());
    system.out.println(arraylist.class.getclassloader());
    
    
    //演示java 提供的類加載器關系
    classloader classloader = classloadertest.class.getclassloader();
    while(classloader != null)
    {
      system.out.print(classloader.getclass().getname()+"-->");
      classloader = classloader.getparent();
    }
    system.out.println(classloader);
    
  }
 
}

輸出結果為:

sun.misc.launcher$appclassloader-->sun.misc.launcher$extclassloader-->null

通過這段程序可以看出來,classloadertest由appclassloader加載,appclassloader的父類節點是extclassloader,extclassloader的父節點是bootstrap。

JAVA提高第七篇 類加載器解析

每一個類加載器都有自己的管轄范圍。 bootstrap根節點,只負責加載rt.jar里的類,剛剛那個system就是屬于rt.jar包里面的,extclassloader負責加載jre/lib/ext/*.jar這個目錄文件夾下的文件。而appclassloader負責加載classpath目錄下的所有jar文件及目錄。
最后一級是我們自定義的加載器,他們的父類都是appclassloader。

二、類加載器的雙親委派機制

除了系統自帶了類加載器,我們還可以自定義類加載器。然后把自己的類加載器掛在樹上。作為某個類加載器的孩子。所有自定義類加載器都要繼承classloader。實現里面的一個方法classloader()如下:

JAVA提高第七篇 類加載器解析

通過上面的知識,我們知道java提供了三個類加載器,而且我們也可以自定義類加載器,并且通過上面的類加載圖也看到了之前的關系,那么對于一個類的.class 到底是誰去加載呢?

當java虛擬機要加載第一個類的時候,到底派出哪個類加載器去加載呢?

(1). 首先當前線程的類加載器去加載線程中的第一個類(當前線程的類加載器:thread類中有一個get/setcontextclassloader(classloader cl);方法,可以獲取/指定本線程中的類加載器)

(2). 如果類a中引用了類b,java虛擬機將使用加載類a的類加載器來加載類b

(3). 還可以直接調用classloader.loadclass(string classname)方法來指定某個類加載器去加載某個類

每個類加載器加載類時,又先委托給其上級類加載器當所有祖宗類加載器沒有加載到類,回到發起者類加載器,還加載不了,則會拋出classnotfoundexception,不是再去找發起者類加載器的兒子,因為沒有getchild()方法。例如:如上圖所示: myclassloader->appclassloader->ext->classloader->bootstrap.自定定義的myclassloader1首先會先委托給appclassloader,appclassloader會委托給extclassloader,extclassloader會委托給bootstrap,這時候bootstrap就去加載,如果加載成功,就結束了。如果加載失敗,就交給extclassloader去加載,如果extclassloader加載成功了,就結束了,如果加載失敗就交給appclassloader加載,如果加載成功,就結束了,如果加載失敗,就交給自定義的myclassloader1類加載器加載,如果加載失敗,就報classnotfoundexception異常,結束。

這樣的好處在哪里呢?可以集中管理,不會出現多份字節碼重復的現象。有兩個類要再在system,如果讓底層的類加載器加載,可能會出現兩份字節碼。而都讓爺爺加載,爺爺加載到已有,當再有請求過來的時候,爺爺說:哎,我加載過啊,直接把那份拿出來給你用啊。就不會出現多份字節碼重復的現象。

現在有一道面試題:能不能自己寫一套java.lang.system.?

分析:你寫了也白寫,因為類加載器加載,直接到爺爺那里去找,找成功了,分本就不回來理你的那個。
答案:通常不可以,因為委托機制委托給爺爺,爺爺在rt.jar包加載到這個類以后就不會加載你自己寫了那個system類了。但是,我也有辦法加載,我寫一個自己的類加載器,不讓他用委托機制,不委托給上級了,就可以了.

因為system類,list,map等這樣的系統提供jar類都在rt.jar中,所以由bootstrap類加載器加載,因為bootstrap是祖先類,不是java編寫的,所以打印出class為null

對于classloadertest類的加載過程,打印結果也是很清楚的。

三、自定義類加載器

下面來看一下怎么定義我們自己的一個類加載器myclassloader:

自定義的類加載器必須繼承抽象類classloader然后重寫findclass方法,其實他內部還有一個loadclass方法和defineclass方法,這兩個方法的作用是:

loadclass方法的源代碼:

?
1
2
3
public class<?> loadclass(string name) throws classnotfoundexception {
    return loadclass(name, false);
  }

再來看一下loadclass(name,false)方法的源代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
protected class<?> loadclass(string name, boolean resolve)throws classnotfoundexception{
     //加上鎖,同步處理,因為可能是多線程在加載類
     synchronized (getclassloadinglock(name)) {
       //檢查,是否該類已經加載過了,如果加載過了,就不加載了
       class c = findloadedclass(name);
       if (c == null) {
         long t0 = system.nanotime();
         try {
           //如果自定義的類加載器的parent不為null,就調用parent的loadclass進行加載類
           if (parent != null) {
             c = parent.loadclass(name, false);
           } else {
             //如果自定義的類加載器的parent為null,就調用findbootstrapclass方法查找類,就是bootstrap類加載器
             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();
           //如果parent加載類失敗,就調用自己的findclass方法進行類加載
           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;
     }
   }

在loadclass代碼中也可以看到類加載機制的原理,這里還有這個方法findbootstrapclassornull,看一下源代碼:

?
1
2
3
4
5
6
private class findbootstrapclassornull(string name)
  {
    if (!checkname(name)) return null;
 
    return findbootstrapclass(name);
  }

就是檢查一下name是否是否正確,然后調用findbootstrapclass方法,但是findbootstrapclass方法是個native本地方法,看不到源代碼了,但是可以猜測是用bootstrap類加載器進行加載類的,這個方法我們也不能重寫,因為如果重寫了這個方法的話,就會破壞這種委托機制,我們還要自己寫一個委托機制。

defineclass這個方法很簡單就是將class文件的字節數組編程一個class對象,這個方法肯定不能重寫,內部實現是在c/c++代碼中實現的findclass這個方法就是根據name來查找到class文件,在loadclass方法中用到,所以我們只能重寫這個方法了,只要在這個方法中找到class文件,再將它用defineclass方法返回一個class對象即可。

這三個方法的執行流程是:每個類加載器:loadclass->findclass->defineclass

前期的知識了解后現在就來實現了

首先來看一下需要加載的一個類:classloaderattachment.java:

?
1
2
3
4
5
6
7
8
9
10
package study.javaenhance;
 
public class classloaderattachment {
  @override
  public string tostring() {
    return "hello classloader!";
    
  }
 
}

這個類中輸出一段話即可:編譯成classloaderattachment.class

再來看一下自定義的myclassloader.java:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package study.javaenhance;
 
import java.io.bytearrayoutputstream;
import java.io.fileinputstream;
import java.io.fileoutputstream;
import java.io.inputstream;
import java.io.outputstream;
 
public class myclassloader extends classloader
{
  //需要加載類.class文件的目錄
  private string classdir;
   
  //無參的構造方法,用于class.newinstance()構造對象使用
  public myclassloader(){
  }
   
  public myclassloader(string classdir){
    this.classdir = classdir;
  }
 
  @override
  protected class<?> findclass(string name) throws classnotfoundexception {
    system.out.println(name);
    string classpathfile = classdir + "\\" + name.substring(name.lastindexof(".")+1) + ".class";
    system.out.println(classpathfile);
    try
    {
      system.out.println("my");
       //將class文件進行解密
      fileinputstream fis = new fileinputstream(classpathfile);
      bytearrayoutputstream bos = new bytearrayoutputstream();
      encodeanddecode(fis,bos);
      byte[] classbyte = bos.tobytearray();
      //將字節流變成一個class
      return defineclass(classbyte,0,classbyte.length);
    } catch (exception e)
    {
      e.printstacktrace();
    }
    
    return super.findclass(name);
  }
  
   //測試,先將classloaderattachment.class文件加密寫到工程的class_temp目錄下
  public static void main(string[] args) throws exception{
    //配置運行參數
    
    string srcpath = args[0];//classloaderattachment.class原路徑
    string despath = args[1];//classloaderattachment.class輸出的路徑
    string desfilename = srcpath.substring(srcpath.lastindexof("\\")+1);
    string despathfile = despath + "/" + desfilename;
    fileinputstream fis = new fileinputstream(srcpath);
    fileoutputstream fos = new fileoutputstream(despathfile);
    //將class進行加密
    encodeanddecode(fis,fos);
    fis.close();
    fos.close();
  }
  
  
 
   /**
   * 加密和解密算法
   * @param is
   * @param os
   * @throws exception
   */
  private static void encodeanddecode(inputstream is,outputstream os) throws exception{
    int bytes = -1;
    while((bytes = is.read())!= -1){
      bytes = bytes ^ 0xff;//和0xff進行異或處理
      os.write(bytes);
    }
  }
}

這個類中定義了一個加密和解密的算法,很簡單的,就是將字節和oxff異或一下即可,而且這個算法是加密和解密的都可以用!

當然我們還要先做一個操作就是,將classloaderattachment.class加密后的文件存起來,也就是在main方法中執行的,這里我是在項目中新建一個

JAVA提高第七篇 類加載器解析

同時采用的是參數的形式來進行賦值的,所以在運行的myclassloader的時候要進行輸入參數的配置:右擊myclassloader->run as -> run configurations

 JAVA提高第七篇 類加載器解析

第一個參數是classloaderattachment.class文件的源路徑,第二個參數是加密后存放的目錄,運行myclassloader之后,刷新class_temp文件夾,出現了classloaderattachment.class,這個是加密后的class文件。

下面來看一下測試類: 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package study.javaenhance;
 
import java.util.arraylist;
 
public class classloadertest
{
  public static void main(string[] args) throws exception
  {
    //獲取類加載器,那么這個獲取的是一個實例對象,我們知道類加載器也有很多種,那么因此也有其對應的類存在,因此可以獲取到對應的字節碼
    system.out.println(classloadertest.class.getclassloader());
    //獲取類加載的字節碼,然后獲取到類加載字節碼的名字
    system.out.println(classloadertest.class.getclassloader().getclass().getname());
    //下面我們看下獲取非我們定義的類,比如system arraylist 等常用類
    system.out.println(system.class.getclassloader());
    system.out.println(arraylist.class.getclassloader());
    
    
    //演示java 提供的類加載器關系
    classloader classloader = classloadertest.class.getclassloader();
    while(classloader != null)
    {
      system.out.print(classloader.getclass().getname()+"-->");
      classloader = classloader.getparent();
    }
    system.out.println(classloader);
    
    
    
    try {
      //class classdate = new myclassloader("class_temp").loadclass("classloaderattachment");
      class classdate = new myclassloader("class_temp").loadclass("study.javaenhance.classloaderattachment");
      object object = classdate.newinstance();
      //輸出classloaderattachment類的加載器名稱
      system.out.println("classloader:"+object.getclass().getclassloader().getclass().getname());
      system.out.println(object);
    } catch (exception e1) {
      e1.printstacktrace();
    }
  }
 
}

結果如下:

sun.misc.launcher$appclassloader@6b97fd
sun.misc.launcher$appclassloader
null
null
sun.misc.launcher$appclassloader-->sun.misc.launcher$extclassloader-->null
classloader:sun.misc.launcher$appclassloader
hello classloader!

這個時候我們會發現調用的app 的類加載器然后輸出了結果,這個是正常的,因為這個時候會采用雙親委派機制。

那么這個時候,我們將自己生成的classloaderattachemet class文件,覆蓋掉編譯的時候生成的class 文件看下結果如何,如果正常應該會報錯,因為這個時候走雙親委派機制在對應的classpath 是可以找到這個class 文件,因此app類加載器會處理,但是因為我們的class 是加密的因此會報錯,運行結果如:

JAVA提高第七篇 類加載器解析

那么如何讓其走到我們自定義的類加載器呢,只需要將編譯時候生成的目錄下的.class 文件刪掉即可,那么這個是app加載不到,則會去調用findclass ,然后就會走到我們定義的類加載器中,運行結果如下:

JAVA提高第七篇 類加載器解析

參考資料:

張孝祥老師java增強視頻

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://www.cnblogs.com/pony1223/p/7711092.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国语刺激对白勾搭视频在线观看 | www.毛片在线观看 | 麻麻与子乱肉小说怀孕 | 国产精视频| 美女把小内内脱个精光打屁屁 | 日本888xxxx| 精品国产国产精2020久久日 | 久久亚洲精选 | 腿交hd| 欧美一区精品二区三区 | 天天射寡妇射 | 国产精品毛片久久久久久久 | 精品欧美一区二区在线观看欧美熟 | 99视频在线观看免费视频 | 99视频有精品视频免费观看 | 国产综合亚洲欧美日韩一区二区 | 紧身牛仔裤美女被啪啪久久网 | 午夜秀场在线观看 | 99色亚洲 | 免费视频网 | 粗了大了 整进去好爽视频 刺激一区仑乱 | 国产网站免费在线观看 | 无码人妻99久久密AV | 无限资源在线观看播放 | 大肥婆丰满大肥奶bbw肥 | 三上悠亚精品专区久久 | 12一14性水蜜桃 | 激情三级做爰在线观看激情 | 国产精品天天影视久久综合网 | 欧美人与禽交片在线播放 | 国产视频一区二 | 极品主播的慰在线播放 | 香蕉免费一区二区三区在线观看 | 亚洲天堂网站在线 | 男人把大ji巴放进男人免费视频 | 精品亚洲视频在线观看 | 国产福利不卡视频 | 国产亚洲女在线精品 | 国产91精选学生在线观看 | 美女和男人差差 | 国产自产2023最新麻豆 |