`
ywu
  • 浏览: 456930 次
  • 性别: Icon_minigender_1
  • 来自: 无锡
社区版块
存档分类
最新评论

(三)类加载器

阅读更多

类加载器的作用是完成类加载过程中的装载步骤,即将.class文件加载到JVM

 

有两种类型的类加载器

1、JVM自带的类加载器

根类加载器(Bootstrap)

扩展类加载器(Extension)

系统类加载器(System)

2、用户自定义类加载器

扩展自java.lang.ClassLoader

 

这些类加载器以父子关系的形式存在



 

最顶层的类加载器,根类加载器,负责加载java的核心类,它加载的路径是jre/lib/rt.jar,我们用到的java核心类如集合框架类ArrayList以及String类等,都是在rt.jar中,即这些类是被BootStrap ClassLoader加载的;BootStrap ClassLoader 是用C++实现的;

Extension ClasssLoader负责加载jre的扩展类,它加载的路径是jre/lib/ext,它的父类加载器是BootStrap ClassLoader;

System ClassLoader从CLASSPATH环境变量指定的路径加载类,一般我们自己编写的类都是由System ClassLoader加载,System ClassLoader的父类加载是Extension ClasssLoader。

除了BootStrap ClassLoader外,其他的ClassLoader(包括用户自定义的)都需要继承java.lang.ClassLoader

 
类加载器在加载类时,采取父委托机制,即加载类时,先请求父类加载器去加载,如果能加载,则在父类加载器加载完后直接返回,如果父类加载器无法加载,则自身尝试去加载,比如,加载我们自己编写的com.jiangnan.classloader.load.User这个类,首先由System ClassLoader加载,System ClassLoader委托父类加载器Extension ClasssLoader加载,Extension ClasssLoader委托BootStrap ClassLoader加载,BootStrap ClassLoader加载不到,Extension ClasssLoader也加载不到,System ClassLoader尝试自己加载,从CLASSPATH环境变量指定的路径加载,如果该路径下有这个类,则加载进来,如果找不到,抛出java.lang.ClassNotFoundException
 

JVM规范允许类加载器在预料到某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误,如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

 

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

 

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类的加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。

 

同一个命名空间内的类是互相可见的

子加载器的命名空间包含所有父加载器的命名空间,因此,由子加载器加载的类能看见父加载器加载的类,例如,系统类加载器加载的类能看到根类加载器加载的类

由父加载器加载的类不能看见子加载器加载的类

如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见

 

接下来看几个测试

创建两个类User、Person,代码如下:

public class User {
    public User() {
        System.out.println("User loaded by " + this.getClass().getClassLoader());
        new Person();
    }
} 
public class Person {
    public Person() {
        System.out.println("Person loaded by " + this.getClass().getClassLoader());
    }

}

分别在构造器里输出加载当前类的ClassLoader对象,在User的构造器中创建了Person对象

 

测试1:

    @Test
    public void testClassLoader1() {
        System.out.println(this.getClass().getClassLoader());
        new User();
        System.out.println("String loaded by " + String.class.getClassLoader());

    } 

控制台输出如下:

sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@addbf1

String loaded by null 

可以看出,当前测试类以及User、Person类都是由系统类加载器加载的,而String类是由根类加载器加载的,由于根类加载器是由C++实现的,所以在代码中获取会返回null

 

测试2:

    @Test
    public void testClassLoader2() {
        ClassLoader classLoader = this.getClass().getClassLoader();
        while(true) {
            System.out.println(classLoader);
            if (null == classLoader) {
                break;
            }
            classLoader = classLoader.getParent();
        }

    }  

从加载当前测试类的类加载器开始,输出父类加载器,控制台输出如下:

sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$ExtClassLoader@42e816

null 

可以看出,类加载器采用父子关系

 

接下来自定义一个类加载器,从指定的loadPath路径加载class,代码如下:

 public class MyClassLoader extends ClassLoader {

    private String name;
    /**加载路径*/
    private String loadPath;
    private static final String EXTENSION = ".class";
    
    public MyClassLoader(String name, String loadPath) {
        super();
        this.name = name;
        this.loadPath = loadPath;
    }
    
    public MyClassLoader(String name, String loadPath, ClassLoader parent) {
        super(parent);
        this.name = name;
        this.loadPath = loadPath;
    }
    
    @Override
    protected Class<?> findClass(String namethrows ClassNotFoundException {
        byte[] b = loadClassData(name);
        if (null == b || b.length == 0) {
            throw new ClassNotFoundException();
        }
        return defineClass(nameb, 0, b.length);
    }
    
    /**
     * 加载读取class文件
     * @param name
     * @return
     */
    private byte[] loadClassData(String name) {
        String dir = name.replace(".", File.separator);
        String realPath = this.loadPath + File.separator + dir + EXTENSION;
        ByteArrayOutputStream baos = null;
        DataInputStream dis = null;
        int end = 0;
        try {
            baos = new ByteArrayOutputStream();
            dis = new DataInputStream(new FileInputStream(realPath));
            while ((end = dis.read()) != -1) {
                baos.write(end);
            }
        } catch (IOException e) {
            //e.printStackTrace();
        } finally {
            if (null != baos) {
                try {
                    baos.close();
                } catch (IOException e) {
                }
            }
            if (null != dis) {
                try {
                    dis.close();
                } catch (IOException e) {
                }
            }
        }
        return baos.toByteArray();
    }
    @Override
    public String toString() {
        return this.name;
    }

}

类加载器在加载类时,会调用ClassLoader的loadClass(String name),查看jdk源码,loadClass方法中会调用父类加载器去加载,如果加载不到,会调用findClass(String name)自行加载,因此自定义的类加载器都需要覆盖findClass(String name)方法,以下为jdk中ClassLoader的loadClass方法的部分源码:
if (c == null) {
        try {
        if (parent != null) {
            c = parent.loadClass(namefalse);
        } 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.
            c = findClass(name);
        }
    }  
创建测试类,如下:
public class ClassLoaderTest1 {
    
    public static void main(String[] args) {
        MyClassLoader classLoader1 = new MyClassLoader("loader1""E:\\classloader\\path1");
        Class<?> clazz1;
        try {
            System.out.println("---------1----------");
            clazz1 = classLoader1.loadClass("com.jiangnan.classloader.classloader.User");
            clazz1.newInstance();
        } catch (Exception e) {
            System.out.println(e.getClass().getName());
        }
        System.out.println("---------1----------");
        MyClassLoader classLoader2 = new MyClassLoader("loader2""E:\\classloader\\path2"classLoader1);
        Class<?> clazz2;
        try {
            System.out.println("---------2----------");
            clazz2 = classLoader2.loadClass("com.jiangnan.classloader.classloader.User");
            clazz2.newInstance();
        } catch (Exception e) {
            System.out.println(e.getClass().getName());
        }
        System.out.println("---------2----------");
    }
}  
创建了两个MyClassLoader的实例,其中classLoader1是classLoader2的父类加载器,分别从指定的路径加载User类,运行此main方法,控制台输出如下:
---------1----------
User loaded by sun.misc.Launcher$AppClassLoader@addbf1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by sun.misc.Launcher$AppClassLoader@addbf1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2---------- 
两次都是由应用类加载器加载的,因为classLoader2的父加载器为应用类加载器,根据父委托机制,应用类加载器在CLASSPATH路径下能加载到User类
 
将工程bin目录下的User.class文件删除,放到 E:\\classloader\\path1,注意,需要按照指定的包目录存放,例如,在我的环境中,包名是com.jiangnan.classloader.classloader,因此,User.class被放在E:\classloader\path1\com\jiangnan\classloader\classloader目录下
现在的情况是,测试类ClassLoaderTest1、自定义类加载器类MyClassLoader以及Person类位于工程bin目录下,二User类位于path1路径下,运行main方法,控制台输出如下:
---------1----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2----------  
User类是由classLoader1实例加载的,因为此时系统类加载器在bin目录下加载不到User类
 
在main方法中增加classLoader3实例,如下:
MyClassLoader classLoader3 = new MyClassLoader("loader3""E:\\classloader\\path3"null);
        Class<?> clazz3 = null;
        try {
            System.out.println("---------3----------");
            clazz3 = classLoader3.loadClass("com.jiangnan.classloader.classloader.User");
            clazz3.newInstance();
        } catch (Exception e) {
            System.out.println(e.getClass().getName());
        }
        System.out.println("---------3----------");
        
        System.out.println("clazz1 == clazz2 is " + (clazz1 == clazz2));
        System.out.println("clazz1 == clazz3 is " + (clazz1 == clazz3)); 
指定classLoader3的父类加载器为null,即classLoader3的类加载器为BootStrap根类加载器,同时判断clazz1和clazz2、clazz3是否相等,控制台输出如下:
---------1----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2----------
---------3----------
User loaded by loader3
Person loaded by loader3
---------3----------
clazz1 == clazz2 is true
clazz1 == clazz3 is false
clazz1与clazz3不相等,因为它们是被不同的类加载器加载,它们在内存中的存在形式如下:


 
 
测试代码位于https://github.com/ywu2014/ClassLoader
  • 大小: 24.7 KB
  • 大小: 22.6 KB
分享到:
评论

相关推荐

    java类加载器

    类加载器的设计遵循双亲委派模型,它分为三个主要部分:启动类加载器、扩展类加载器和应用类加载器。 #### 二、类加载过程 类加载过程主要包括三个步骤: 1. **加载**:通过类的全限定名找到该类的二进制字节流。...

    类加载器文件

    #### 三、类加载器的基本概念 1. **Bootstrap ClassLoader**: 这是由JVM内部实现的类加载器,主要用于加载核心Java类库(即所有以`java.*`开头的类)。它是用本地代码实现的,并不继承自`java.lang.ClassLoader`。 ...

    自定义类加载器实现自定义加载

    - Java中的类加载器采用双亲委派模型,即一个类首先由启动类加载器Bootstrap ClassLoader尝试加载,如果找不到则交给扩展类加载器Extension ClassLoader,再找不到则交由应用程序类加载器AppClassLoader,最后如果...

    类加载器(java)

    这个层次结构通常由三个主要的类加载器组成: 1. **bootstrap class loader**:这是最基础的类加载器,由C++实现,负责加载JRE核心库,如rt.jar,它包含了Java语言的基础类库,如`java.lang.Object`等。 2. **...

    类加载器代码

    类加载器是Java虚拟机(JVM)的重要组成部分,它负责将类的字节码从文件系统或网络中加载到JVM中,并转换为运行时的java.lang.Class对象。类加载器不仅涉及程序的启动,还关系到类的动态加载、类间的隔离以及安全性...

    JVM类加载器说明文档

    系统类加载器在加载类时,会先尝试让扩展类加载器加载,如果扩展类加载器无法加载,则再由系统类加载器自己尝试加载。这样的设计是为了保证核心类库的唯一性和安全性,避免用户自定义的类覆盖了 JDK 内置的核心类。 ...

    ClassLoader类加载器

    1. Bootstrap ClassLoader:这是最基础的类加载器,由JVM本身实现,负责加载JRE的`&lt;JAVA_HOME&gt;/lib`目录下的核心类库,或者被`-Xbootclasspath`参数指定的路径中的类。 2. Extension ClassLoader:扩展类加载器,...

    java 类加载器 加密

    Java中的类加载器主要有三种:Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。它们按照双亲委派模型工作,从根加载器开始,逐级尝试加载类。当我们想要对类进行加密时,可以创建一个自定义的类...

    网络类加载器实现

    默认的类加载器包括bootstrap classloader(引导类加载器)、extension classloader(扩展类加载器)和appclassloader(应用程序类加载器),它们按照双亲委托模型进行工作,从基础到具体逐层尝试加载类。...

    java类加载器-tomcat中的类加载器

    在Java中,类加载器主要分为三个层次:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap ClassLoader负责加载JDK的核心库,如rt.jar;Extension ClassLoader则加载JRE扩展目录下的jar文件...

    ModRunJava类加载器可以直接从Maven存储库加载并运行类

    Java开发中的类加载器是Java运行环境的核心组件之一,它负责查找、加载和初始化类文件。在传统的Java应用中,类通常是从硬盘上的类路径(ClassPath)或模块路径(ModulePath)中加载的。然而,随着开发模式的演变,...

    java类加载器学习三、类加载器的委托模式

    java类加载器学习三、类加载器的委托模式

    深入Java虚拟机_002_深入详解JVM之类加载器深度剖析、根、扩展及系统类加载器

    本部分我们将深入探讨JVM中的类加载器,特别是根类加载器、扩展类加载器和系统类加载器。 首先,让我们了解类加载的基本过程。当JVM启动时,会触发类加载。这个过程分为三个阶段:加载、链接和初始化。加载阶段,类...

    RAD类加载器序列

    #### 三、类加载器模式 在WebSphere Application Server中,有两种主要的类加载器模式: 1. **PARENT_FIRST**:这种模式下,父类加载器会先于子类加载器加载类。这种方式有利于保持应用程序的一致性,但是可能导致...

    JAVA ClassLoader 讲解 (类加载器)

    Java的类加载器体系结构由三个主要类型的类加载器组成: 1. **引导类加载器(Bootstrap ClassLoader)**:这是最基础的类加载器,负责加载Java的核心库类,如`java.lang`包下的类。 2. **扩展类加载器(Extension ...

    java的ClassLoader类加载器机制

    类加载器的工作原理可以分为三个阶段:加载、链接和初始化。在加载阶段,类加载器会查找和加载指定的类。在链接阶段,类加载器会对加载的类进行链接,包括验证、准备和解析。在初始化阶段,类加载器会对加载的类进行...

    关于Java类加载器的探讨.pdf

    Java中有三种类加载器:引导类加载器、扩展类加载器和系统类加载器。 1. 引导类加载器:负责加载jdk中的系统类,如String、Integer等,这些类都是由引导类加载器加载的。引导类加载器是用C语言实现的,在Java程序中...

    深入探讨 Java 类加载器

    除了这三种系统类加载器,还可以自定义类加载器,满足特定的加载需求,例如加载网络上的类或加密后的类文件。 类加载器的代理模式体现在父子加载器的关系上。当一个类加载器尝试加载类时,它会先委托给父类加载器,...

    14.类加载器1

    本文将深入探讨三种核心的类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)以及应用程序类加载器(Application ClassLoader)。此外,还将详细介绍双亲委托模型及其工作...

    类加载器加载过程.rar

    在Java编程语言中,类加载器(ClassLoader)是至关重要的组成部分,它负责将类的字节码转换为可执行的Java对象。理解类加载器的工作原理对于深入掌握Java虚拟机(JVM)运行机制和进行高效的问题排查至关重要。在这个...

Global site tag (gtag.js) - Google Analytics