ClassLoade学习记录

来源:互联网 发布:xampp mysql 密码 编辑:程序博客网 时间:2024/05/02 14:22

基本介绍

类加载器,功能是负责读取Java字节码代码(.class文件),并转换成java.lang.Class类的一个实例(每个实例代表一个Java类)并加载到JVM中。其中Java字节码的来源不只是编译的.class文件也有通过动态代理生成的动态类、从网络下载等其他来源。

 

基本上所有的类加载器都是java.lang.ClassLoader类的子类。类加载器除上述功能外还负责加载Java应用所需的资源(图片、配置文件等)。

 

分类

类加载器可以分为两大类:一类是JDK提供的,负责加载J2SE的基础Java类;另一类是Java应用开发人员开发的,负责特殊的加载逻辑,例如Spring的类加载器。

JDK提供的类加载器主要有如下3个:

l  引导类加载器(bootstrapclass loader),用来加载Java的核心类库(通常从rt.jar加载),是虚拟机的一部分,通常使用C语言实现,与ClassLoader类无继承关系。(例如System.out.println(String.class.getClassLoader());的输出为null)

l  扩展类加载器(extensionsclass loader),用来加载Java的扩展库,这个扩展库专门指JDK的扩展库,从jre/lib/ext目录查找并加载类库,由Java语言实现。(例如System.out.println(EventID.class.getClassLoader());的输出为sun.misc.Launcher$ExtClassLoader@68e86f41)

l  系统类加载器(systemclass loader),也称为应用类加载器,根据Java应用的类路径(ClassPath)来查找并加载Java类,由Java语言实现。(例如System.out.println(com.sun.javadoc.Doc.class.getClassLoader());的输出为sun.misc.Launcher$ExtClassLoader@68e86f41)

处理上边描述的三种JDK提供的类加载器外,开发人员也可以自己开发自己的类加载器(继承自ClassLoder类)。我们最常用到的就是Tomcat的类加载器以及Spring的类加载器。

 

备注:

当对jre/lib/ext类使用的时候,默认会出现Access restriction: The type * is not API的编译错误。

原因是Eclipse 默认把这些受访问限制的API设成了ERROR。

解决方法1:只要把Windows-Preferences-Java-Complicer- Errors/Warnings里面的Deprecatedand restricted API中的Forbidden references(access rules)选为Warning就可以编译通过。

解决方法2:这个时候需要将项目中的JRE System Library删除掉,然后重新加入。

推荐第二种。

类加载器的层级结构

类加载器有一种父子关系。除了引导类加载器外,每个类加载器都有一个父类加载器。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java类的类加载器。因为类加载器 Java类如同其它的 Java类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。

父类加载器测试:

public class ClassLoaderTest {

    public static void main(String[] args) {

//     System.out.println(String.class.getClassLoader());

//     System.out.println(EventID.class.getClassLoader());

//     System.out.println(System.getenv("classpath"));

//     System.out.println(Doc.class.getClassLoader());

       ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();

       while(classLoader!=null){

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

           classLoader = classLoader.getParent();

       }

    }

}

输出为:

sun.misc.Launcher$AppClassLoader@4e77b794

sun.misc.Launcher$ExtClassLoader@15b57dcb

ExtClassLoader的父类就是引导类加载器,为null。

层次结构图:

类加载器加载规则

当类加载器尝试加载某个类时,会遵守先交由其父类加载器尝试加载,以此类推,无法加载后才由自身加载的规则:

ClassLoader类源码:

protected Class<?> loadClass(Stringname, booleanresolve)

        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) {

                    // ClassNotFoundExceptionthrown if class not found

                    // from the non-null parentclass loader

                }

                if (c ==null) {

                    // If still not found, then invoke findClass in order

                    // to find the class.

                    longt1 = 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);

            }

            returnc;

        }

    }

类加载器这种父类优先加载的规则主要是为了保证类的唯一性,在Java中判断两个类是否相同(注意这里是类而不是类的实例),除了判断它们的全类名是否相同还有一点是加载它们的类加载器是否是一个,只有两个都相同,才认为两个类是相同的(可以将类加载器理解为类的命名空间):

    public static void testForClassEquels() {

       String classDataRootPath ="D:\\";

       FileSystemClassLoader fscl1 =new FileSystemClassLoader(

              classDataRootPath);

       FileSystemClassLoader fscl2 =new FileSystemClassLoader(

              classDataRootPath);

       String className = "com.h3c.test.Test";

       try {

           Class<?> class1 = fscl1.loadClass(className);

           System.out.println(class1.getClassLoader());

           Object obj1 = class1.newInstance();

           Class<?> class2 = fscl2.loadClass(className);

           System.out.println(class2.getClassLoader());

           Object obj2 = class2.newInstance();

           Method setSampleMethod =class1.getMethod("setTest",

                  java.lang.Object.class);

           setSampleMethod.invoke(obj1,obj2);

       } catch (Exceptione) {

           e.printStackTrace();

       }

    }

输出:

com.h3c.test.FileSystemClassLoader@7a4014a0

com.h3c.test.FileSystemClassLoader@12504e0

java.lang.reflect.InvocationTargetException

    atsun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

    atsun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

    atsun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

    atjava.lang.reflect.Method.invoke(Unknown Source)

    atcom.h3c.test.ClassLoaderTest.testForClassEquels(ClassLoaderTest.java:50)

    atcom.h3c.test.ClassLoaderTest.main(ClassLoaderTest.java:31)

Caused by: java.lang.ClassCastException: com.h3c.test.Test cannot be cast to com.h3c.test.Test

    atcom.h3c.test.Test.setTest(Test.java:8)

    ... 6more

 

如果使用同一个类加载器加载:

com.h3c.test.FileSystemClassLoader@6adcc4e2

com.h3c.test.FileSystemClassLoader@6adcc4e2

只有能够保证类的唯一性才能保证Java的单根继承,避免多个版本的Object或其他类出现。

多线程情况下的类加载

多线程情况下,线程将继承其父线程的上下文类加载器。Java应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。待测试

Class.forName

简单的类加载。。。JDBC常用。

Web容器的类加载器

Tomcat6的启动过程:

1java虚拟机的bootstrap loader加载java的核心类库。java虚拟机使用JAVA_HOME环境变量来定位核心库的位置。

2Startup.sh,使用start参数调用Catalina.sh,重写系统的classpath并加载bootstrap.jartomcat-juli.jar。这些资源仅对Tomcat可见。

3、为每一个部署的Context创建Class loader,加载位于每个web应用程序WEB-INF/classesWEB-INF/lib目录下的所有类和jar文件。每个web应用程序仅仅可见自己目录下的资源。

4Common classloader加载位于$CATALINA_HOME/lib目录下的所有类和jar文件,这些资源对所有应用程序和Tomcat可见。

WEB容器中类加载器加载规则和顺序和上文描述的类加载器加载规则相反,即首先尝试去加载某个类,如果找不到再代理给父类加载器。但是Java核心库的类是不在查找范围之内的。这也是为了保证 Java核心库的类型安全。

OSGi中的类加载器

OSGi 中的每个模块(bundle)都包含 Java包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi特有的类加载器机制来实现的。OSGi中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java包和类。当它需要加载 Java核心库的类时(以 java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java类时,它会代理给导出此 Java类的模块来完成加载。模块也可以显式的声明某些 Java包和类,必须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation的值即可。

0 0