关于ClassLoader知识整理

基础概念

将CLASS文件加载到JVM中。但是由于JAR包很多,一次加载内存可能会崩溃,所以要动态的加载这些类。

关于CLASS文件

JVM虚拟机只识别class文件,所以如果你用C或者别的语言编写完成徐并编译为class文件,JVM虚拟机也可以识别
命令
javac 编译为.class
java ~ 加载该类

关于环境变量

  • JAVA_HOME
    只是一个JDK的目录路径
    指的是你JDK安装的位置,一般默认安装在C盘,如
C:\Program Files\Java\jdk1.8.0_91
  • PATH
    将程序路径包含在PATH当中后,在命令行窗口就可以直接键入它的名字了,而不再需要键入它的全路径,比如上面代码中我用的到javac和java两个命令。 也就是在原来的JAVA_HOME路径上添加JDK目录下的bin目录和jre目录的bin(我的只添加了JDK路径,JDK应该是包含JRE的)
    JRE只是提供运行环境,即核心类库JVM等,而JDK包含了JRE而且还提供开发工具,即像javac java这样的命令
PATH=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;%PATH%;
  • CLASSPATH
    一看就是指向jar包路径。 我理解为JAVA提供的JAR包都在这里。
CLASSPATH=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar

需要注意的是前面的.;,.代表当前目录。

  • 命令
    查看环境变量
echo %JAVA_HOME%

echo %PATH%

echo %CLASSPATH%

三个类加载器

  • Bootstrap ClassLoader
    最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。(JRE_HOME 使用Eclipse时候取决于给项目设定的JRE环境,你可以选择JDK下的JRE也可以使用独立的JRE,我认为使用命令行编译的时候会默认使用JDK下面的JRE环境,因为没有设置%JRE_HOME%这个变量)。
    java -Xbootclasspath/a:path将指定的path追加到默认的bootstrap路径中。
    测试:System.out.println(System.getProperty("sun.boot.class.path"));

此处JRE环境选择为独立JRE

C:\Program Files\Java\jre1.8.0_111\lib\resources.jar;C:\Program Files\Java\jre1.8.0_111\lib\rt.jar;C:\Program Files\Java\jre1.8.0_111\lib\sunrsasign.jar;C:\Program Files\Java\jre1.8.0_111\lib\jsse.jar;C:\Program Files\Java\jre1.8.0_111\lib\jce.jar;C:\Program Files\Java\jre1.8.0_111\lib\charsets.jar;C:\Program Files\Java\jre1.8.0_111\lib\jfr.jar;C:\Program Files\Java\jre1.8.0_111\classes
  • Extention ClassLoader
    扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
    测试:
        System.out.println(System.getProperty("java.ext.dirs"));
C:\Program Files\Java\jre1.8.0_111\lib\ext;C:\Windows\Sun\Java\lib\ext
  • Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
    测试下

    System.out.println(System.getProperty("java.class.path"));

    结果为

E:\javastudy\测试区\bin

这个路径其实就是当前java工程目录bin,里面存放的是编译生成的class文件。

关于类加载器的父类加载器

测试,编写一个测试类Test,然后

ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());

结果

ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742

这个说明,AppClassLoader的父加载器是ExtClassLoader。那么ExtClassLoader的父加载器又是谁呢?

System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());

结果

ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
java.lang.NullPointerException
    at ClassLoaderTest.main(ClassLoaderTest.java:13)

又是一个空指针异常,这表明ExtClassLoader也没有父加载器,先抛出结论ExtClassLoader的父加载器正是Bootstrap ClassLoader。

Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例,并将ExtClassLoader设置为AppClassLoader的父加载器。(具体如何设置请参考ClassLoader Launcher的源码,此处不叙述)

关于类的加载过程

一个类请求加载的时候,首先查找他的类加载器有没有加载,如果没有加载,则委托给父类类加载器查找其缓存是否加载,一直如此递归到Bootstrap ClassLoader。如果Bootstrap ClassLoader也没有加载(到缓存),那么他就会去自己指定的路径下面去寻找,也就是sun.mic.boot.class 里面找。找到就返回,没有找到,让子类加载器去找。也就是让ExtClassLoader 在自己的路径下java.ext.dirs 下去找,查找成功则返回,否则再让子类加载器去找,即AppClassLoader 去自己指定路径下面java.class.path 去找,找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
我们可以发现委托是从下向上,然后具体查找过程却是自上至下。

重要方法

  • LoadClass()
protected Class<?> loadClass(String name,
                             boolean resolve)
                      throws ClassNotFoundException
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检测类是否已经加载在缓存中
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //父加载器不为空则调用父加载器的loadClass,此处即委托中向上递归的体现
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空则调用Bootstrap Classloader,解释了ExtClassLoader的父类为null指针的原因
                        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();
                    //父加载器的缓存中没有找到,则调用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()
                resolveClass(c);
            }
            return c;
        }
    }

总结下
上面是方法原型,一般实现这个方法的步骤是
1. 执行findLoadedClass(String)去检测这个class是不是已经加载过了。
2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。

自定义ClassLoader

此处只做简要介绍具体请看关于ClassLoeder

  • 步骤
    1.编写一个类继承自ClassLoader抽象类。
    2.复写它的findClass()方法。
    3.在findClass()方法中调用defineClass()。

  • 注意
    一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值