java ClassLoader 基本原理.docx

上传人:b****7 文档编号:10791324 上传时间:2023-02-22 格式:DOCX 页数:21 大小:94.38KB
下载 相关 举报
java ClassLoader 基本原理.docx_第1页
第1页 / 共21页
java ClassLoader 基本原理.docx_第2页
第2页 / 共21页
java ClassLoader 基本原理.docx_第3页
第3页 / 共21页
java ClassLoader 基本原理.docx_第4页
第4页 / 共21页
java ClassLoader 基本原理.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

java ClassLoader 基本原理.docx

《java ClassLoader 基本原理.docx》由会员分享,可在线阅读,更多相关《java ClassLoader 基本原理.docx(21页珍藏版)》请在冰豆网上搜索。

java ClassLoader 基本原理.docx

javaClassLoader基本原理

Ⅰ.类加载器基本概念

顾名思义,类加载器(ClassLoader )用来加载Java类到Java虚拟机中。

一般来说,Java虚拟机使用Java类的方式如下:

∙Java源程序(.java文件)在经过Java编译器编译之后就被转换成Java字节代码(.class文件)。

∙类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例。

每个这样的实例用来表示一个Java类。

通过此实例的newInstance()方法就可以创建出该类的一个对象。

实际的情况可能更加复杂,比如:

Java字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

Ⅱ.Classloader类图

注意:

这是类关系,非对象关系。

基本上所有的类加载器都是java.lang.ClassLoader类的一个实例。

BootstrapClassLoader是一个单独的java类,其实在这里,不应该叫他是一个java类,因为,它已经完全不用java实现了。

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。

除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。

不过本文只讨论其加载类的功能。

为了完成加载类的这个职责,ClassLoader提供了一系列的方法,比较重要的方法如:

getParent()

返回该类加载器的父类加载器。

loadClass(Stringname)

加载名称为name的类。

是加载器加载类的主要方法,也是实现双亲委派模型的主要方法(稍后会详细讲解)。

返回的结果是java.lang.Class类的实例。

findClass(Stringname)

在自己的查找路径中,查找名称为name的类,并调用defineClass方法,加载该类。

返回的结果是java.lang.Class类的实例。

findLoadedClass(Stringname)

在自己的类加载器中,查找名称为name的已经被自己加载过的类。

返回的结果是java.lang.Class类的实例。

defineClass(Stringname,byte[]b,intoff,intlen)

把字节数组b中的内容转换成Java类,返回的结果是java.lang.Class类的实例。

这个方法被声明为final的。

按照《JavaLanguageSpecification》的定义,任何作为String类型参数传递给ClassLoader中方法的类名称都必须是一个二进制名称(上述方法中的"Stringname")。

有效类名称的示例包括:

"java.lang.String"

"javax.swing.JSpinner$DefaultEditor"

"java.security.KeyStore$Builder$FileBuilder$1"

".URLClassLoader$3$1"

需要注意的是内部类的Stringname表示,如com.example.Sample$1和com.example.Sample$Inner等表示方式。

URLClassLoader是ClassLoader的子类,是系统类加载器(appCLassLoader)和扩展类加载器(extClassLoader)的父类(类图关系,非对象关系)。

URLClassLoader非常强大,可以从本地文件系统获取二进制文件来加载类,也可以从远程主机获取二进制文件来加载类。

实际上,在应用程序中,可以直接使用URLClassLoader来加载类,URLClassLoader提供了如下两个构造器:

∙URLClassLoader(URL[]urls):

使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls指定的系列路径来查询并加载类。

∙URLClassLoader(URL[]urls,ClassLoaderparent):

使用指定的父类加载器创建一个ClassLoader对象,其他功能与前一个构造器相同。

Ⅲ.Classloader对象关系图

Java中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由Java应用开发人员编写的。

通常,会对类加载器对象指定一个或多个“目录或文件”,使类加载器只加载这些指定"目录或文件"下的.class文件。

系统提供的类加载器主要有下面三个:

‍‍    1).引导类加载器(bootstrapClassLoader):

       它用来加载 Java的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。

      例如:

Java的核心库一般是JRE目录下的“lib\rt.jar”等等。

         具体路径可以用System.out.println(System.getProperty("sun.boot.class.path"));获取。

    2).扩展类加载器(extClassLoader):

      它用来加载 Java的扩展库。

Java虚拟机的实现会提供一个扩展库目录。

      该类加载器在此目录里面查找并加载Java类。

      例如:

Java的扩展库一般是JRE目录的“jre\lib\ext”等等。

         具体路径可以用System.out.println(System.getProperty("java.ext.dirs"));获取。

    3).系统类加载器(appCLassLoader):

      它根据 Java应用的类路径(CLASSPATH)来加载Java类。

一般来说,Java应用的类都是由它来完成加载的。

      可以通过ClassLoader.getSystemClassLoader()来获取它。

      具体路径可以用System.out.println(System.getProperty("java.class.path"));获取。

‍‍

除了引导类加载器之外,所有的类加载器都有一个父加载器。

通过getParent()方法可以得到。

对于系统提供的类加载器来说,系统类加载器(appCLassLoader)的父加载器是扩展类加载器(extClassLoader),而扩展类加载器(extClassLoader)的父加载器是引导类加载器(bootstrapClassLoader)。

而且,java中的每个Java类都维护着一个指向加载它的classloader的引用,通过getClassLoader()方法就可以获取到此引用,例如ClassLoadercl=Hello.class.getClassLoader()。

对于开发人员编写的类加载器(例如,CustomClassLoaderA)来说,CustomClassLoaderA在类Person中,实例化一个ClassLoader对象:

?

1

2

3

4

5

public class Person {

        public void sayHello(){

            ClassLoader customClassLoaderA=new CustomClassLoaderA();

        }

}

那么customClassLoaderA的父加载器就是加载类Person的加载器,即Person.getClassLoader()。

一般来说,开发人员编写的类加载器的父类加载器是系统类加载器(appCLassLoader)。

所以,类加载器通过这种方式组织起来,形成树状结构。

如下图:

注意:

这是对象关系,非类关系。

代码示例:

通过递归调用getParent()方法来输出全部的父类加载器:

?

1

2

3

4

5

6

7

8

import .URL;

import .URLClassLoader;

 

public class CustomClassloaderA extends URLClassLoader {

    public CustomClassloaderA(URL[] urls) {

    super(urls);

    }

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import .URL;

 

public class TestMain {

 

    public static void main(String[] args) throws ClassNotFoundException {

        URL url = new URL("file:

/D:

/workspace/classfinder/src/com/sunchp");

 

        ClassLoader classloader = new CustomClassloaderA(new URL[] { url });

 

        while (classloader !

= null) {

            System.out.println(classloader.toString());

            classloader = classloader.getParent();

        }

    }

}

运行结果:

com.sunchp.CustomClassloaderA@a574b2

sun.misc.Launcher$AppClassLoader@417470d0

sun.misc.Launcher$ExtClassLoader@439a8942

∙第一个输出的是自定义的类加载器com.sunchp.CustomClassloaderA@a574b2,它是我们自定义的类加载器CustomClassloaderA类的实例;

∙第二个输出的是系统类加载器(appCLassLoader),它是sun.misc.Launcher$AppClassLoader类的实例;

∙第三个输出的是扩展类加载器(extClassLoader),是sun.misc.Launcher$ExtClassLoader类的实例。

需要注意的是这里并没有输出引导类加载器,这是由于有些JDK的实现对于父类加载器是引导类加载器的情况,getParent()方法返回null。

Ⅳ.3种Classloader搜索路径

appCLassLoader和extClassLoader都是URLClassLoader的子类,所以他们实际也需要一个URL作为参考,去载入类。

1).appCLassLoader所参考的URL是从系统参数java.class.path取出来的。

这个参数,可以在我们执行java.exe时,使用-cp或者-classpath或者CLASSPATH环境变量指定:

?

1

2

String s=System.getProperty("java.class.path");

System.out.println(s);

classpath,代表的是当前类目录(或者说当前项目对应的class目录),已经所引用的jar文件。

使用-cp或者-classpath可以改变,相对于系统的CLASSPATH,优先使用-cp或者-classpath。

2).extClassLoader参考的是系统参数java.ext.dirs:

?

1

2

String s=System.getProperty("java.ext.dirs");

System.out.println(s);

3).bootstrapClassLoader参考的是系统参数,sun.boot.class.path:

?

1

2

String s=System.getProperty("sun.boot.class.path");

System.out.println(s);

∙改变appCLassLoader和extClassLoader对应的参数是有意义的,也会影响它们的搜索路径;

∙改变bootstrapClassLoader的参数是没有易用的,bootstrapClassLoader的搜索路径只是与sun.boot.class.path的预设值相同,并不是参考使用了sun.boot.class.path。

∙appCLassLoader和extClassLoader在内存中分别只有一个实例,所以在内存中动态改变参数是行不通的。

∙如果需要加载其他路径的class文件,推荐自定义类加载器,并设置自定义类加载器的搜索路径。

Ⅴ.自定义类加载器

JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。

查阅API文档中关于ClassLoader的方法不难发现,ClassLoader中包含了大量的protected方法——这些方法都可以被子类重写。

ClassLoader类有如下两个关键方法:

∙loadClass(Stringname,booleanresolve):

  该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。

∙findClass(Stringname):

根据二进制名称,在自己的查找路径中,查找名称为name的类,并调用defineClass方法,加载该类。

返回的结果是java.lang.Class类的实例。

如果需要实现自定义的ClassLoader,则可以通过重写以上两个方法来实现,当然,我们推荐重写findClass()方法,而不是重写loadClass()方法。

loadClass()方法的执行步骤如下:

∙用findLoadedClass(Stringname)来检查是否已经加载类,如果已经加载,则直接返回。

∙在父类加载器上调用loadClass()方法。

如果父类加载器为null,则直接使用根类加载器加载。

∙调用findClass(Stringname)方法查找类。

从以上步骤可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托,缓冲机制两种策略;如果重写loadClass()方法,则实现逻辑更为复杂。

在ClassLoader里还有一个核心方法:

ClassdefineClass(Stringname,byte[]b,intoff,intlen),该方法负责将指定类的字节码文件(即Class文件,如Hello.class)读入字节数组byte[]b内,并把它转换为Class对象,该字节码文件可以源于文件、网络等。

defineClass()方法管理JVM的许多复杂的实现,它负责将字节码分析成运行时数据结构,并校验有效性等。

不过不用担心,程序员无须重写该方法。

事实上,该方法是final型,即使我们想重写也没有机会。

除此之外,ClassLoader里还包含如下一些普通方法:

∙findSystemClass(Stringname):

在本地文件系统装入文件。

它在本地文件系统中寻找类文件,如果存在,就使用defineClass()方法将原始字节转换成Class对象,以将该文件转换成类。

∙staticgetSystemClassLoader():

这是一个静态方法,用于返回系统类加载器。

∙getParent():

获取该类加载器的父类加载器。

∙resolveClass(Class

>c):

链接指定的类。

类加载器可以使用此方法来链接类c。

读者无须理会关于此方法的太多细节。

∙findLoadedClass(Stringname):

如果此java虚拟机已经加载了名为name的类,则直接返回该类对应的Class实例,否则返回null。

此方法是java类加载缓存机制的体现。

loadClass()、findClass()、findLoadedClass()和defineClass的关系可以用如下代码表示(这也是实现下述“双亲委派模型”的主要逻辑):

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

Class loadClass(String name,boolean resolve) {

    // 检查类是否已被装载过

    Class c = findLoadedClass(name);

    if (c == null) {

        // 指定类未被装载过

        try {

            if (parent !

= null) {

                // 如果父类加载器不为空, 则委派给父类加载

                c = parent.loadClass(name, false);

            } else {

                // 如果父类加载器为空, 则委派给启动类加载加载

                c = findBootstrapClass0(name);

            }

        } catch (ClassNotFoundException e) {

            // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其

            // 捕获, 并通过findClass方法,调用defineClass(), 由自身加载

            c = findClass(name);

        }

    }

}

Ⅵ.双亲委派模型

所谓的双亲委派模型,就是,classloader实例当有类需要载入时,会先让parent采用他的搜寻路径去加载类,如果parent加载不到,那么才由自己去加载。

其实,所谓双亲委派模型只有两个过程:

∙首先,自底向上,挨个加载器检查是否已经加载了指定类,直到根加载器(bootstrapClassLoader)。

这个过程中,如果已经加载,那么直接返回该类Class实例的引用。

这个过程主要是findLoadedClass()方法的调用。

∙然后,自顶向下尝试加载类。

如果根加载器(bootstrapClassLoader)也未加载成功该类,那么就会抛出异常,然后自顶向下挨个尝试加载。

直到customClassloader调用findClass()尝试加载。

如果还未加载成功,就抛出ClassNotFoundException给调用者。

代码逻辑:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

Class loadClass() {

    // 检查类是否已被装载过

    Class c = findLoadedClass(name);

    if (c == null) {

        // 指定类未被装载过

        try {

            if (parent !

= null) {

                // 如果父类加载器不为空, 则委派给父类加载

                c = parent.loadClass(name, false);

            } else {

                // 如果父类加载器为空, 则委派给启动类加载加载

                c = findBootstrapClass0(name);

            }

        } catch (ClassNotFoundException e) {

            // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其

            // 捕获, 并通过findClass方法, 由自身加载

            c = findClass(name);

        }

    }

}

一般来说,通过XXXX.class.getClassLoader()或者Class.forName()获取当前类的加载器,然后用该加载器加载目标类,例如“com.sunchp.demo”。

此时,都是以当前类的加载器为起点,依次找它的父亲加载器,进行加载目标类。

(这个地方,对理解后面的“线程上下文类加载器”很重要)

情景一:

?

1

2

ClassLoader cl=XXXX.class.getClassLoader();

cl.loadClass("com.sunchp.Demo");

∙如果XXXX是由自定义加载器加载的,则从该自定义加载器开始查找"com.sunchp.Demo",customClassloader->appClassLoader->extClassLoader->bootstrapClassLoader;

∙如果XXXX是由appClassLoader加载的,则加载过程是appClassLoader->extClassLoader->bootstrapClassLoader;XXXX的加载器,看不到CustomClassLoader。

∙如果XXXX是由extClassLoader加载的,则加载过程是extClassLoader->bootstrapClassLoader;

∙如果XXXX是由bootstrapClassLoader加载的,则加载过程只有bootstrapClassLoader。

情景二:

?

1

2

3

4

5

public class A {

    public void sayHello() {

        Class demoClass = Class.forName("com.sunchp.Demo");

    }

}

因为Class.forName用的是前类的加载器,即A.class.getClassLoader(),所以

∙如果当前类A是由自定义加载器加载的,则从该自定义加载器开始查找"com.sunchp.Demo",customClassloader->appClassLoader->extClassLoader->bootstrapClassLoader;同上。

∙如果当前类A是由appClassLoader加载的,则加载过程是appClassLoader->extClassLoader->bootstrapClassLoader;同上。

∙如果当前类A是

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 实习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1