gpt4 book ai didi

JAVA提高第七篇 类加载器解析

转载 作者:qq735679552 更新时间:2022-09-28 22:32:09 26 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章JAVA提高第七篇 类加载器解析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

今天我们学习类加载器,关于类加载器其实和jvm有很大关系,在这里这篇文章只是简单的介绍下类加载器,后面学习到jvm的时候还会详细讲到类加载器,本文分为下面几个小节讲解:

1、认识类加载器 。

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.

2、类加载器的双亲委派机制 。

除了系统自带了类加载器,我们还可以自定义类加载器。然后把自己的类加载器挂在树上。作为某个类加载器的孩子。所有自定义类加载器都要继承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类的加载过程,打印结果也是很清楚的.

3、自定义类加载器 。

下面来看一下怎么定义我们自己的一个类加载器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 。

  。

第一个参数是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 。

最后此篇关于JAVA提高第七篇 类加载器解析的文章就讲到这里了,如果你想了解更多关于JAVA提高第七篇 类加载器解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com