ClassLoader


一、什么是classloader

       一个Java程序要想运行起来,首先需要经过编译生成 .class文件,然后创建一个运行环境(jvm)来加载字节码文件到内存运行,而.class 文件是怎样被加载中jvm 中的就是Java classloader所做的事情。
        那么.class文件什么时候会被类加载器加载到jvm中运行那?比如执行new操作时候,



二、Java原生的classloader

  1.  BootStrap Classloader:引导类加载器,又称启动类加载器,是最顶层的类加载器,主要用来加载Java核心类,如rt.jar、resources.jar、charsets.jar等,
    Sun的JVM中,执行java的命令中使用-Xbootclasspath选项或使用- D选项指定sun.boot.class.path系统属性值可以指定附加的类,它不是 java.lang.ClassLoader的子类,而是由JVM自身实现的该类c 语言实现,Java程序访问不到该加载器。通过下面代码可以查看该加载器加载了哪些jar包
    [java]  view plain  copy
    1. public void test() {  
    2.         URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();    
    3.         for (int i = 0; i < urls.length; i++) {    
    4.             System.out.println(urls[i].toExternalForm());    
    5.         }   
    6.     }  

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/resources.jar

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/rt.jar

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/sunrsasign.jar

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/jsse.jar

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/jce.jar

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/charsets.jar

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/jfr.jar

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/classes,写到这里大家应该都知道,我们并没有在classpath里面指定这些类的路径,为啥还是能被加载到jvm并使用起来了吧,因为这些是bootstarp来加载的。

       2. Extension Classloader:扩展类加载器,主要 负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar包或者由java.ext.dirs系统属性指定的jar             包。放入这个目录下的jar包对所有的app classloader都是可见的,所以这为使用第三方非核心功能提供了一个标准。
 System.out.println(System.getProperty("java.ext.dirs"));


      3. App  classloader :系统类加载器,又称应用加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。调用ClassLoader.getSystemClassLoader()可以获取该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。执行以下代码即可获得:
System.out.println(System.getProperty("java.class.path"));
输出结果则为用户在系统属性里面设置的CLASSPATH。


      


 classloader 加载类用的是全盘负责委托机制。
所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;
委托机制则是先让parent类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因(但是通过使用新的classloader也可以是修改后的类生效不重启)。






三、类加载器原理

       Java类加载器使用的是委托机制,那么问题来了,为啥使用这种方式那? 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法



四、例子

[java]  view plain  copy
  1. try {  
  2.               
  3.             // bootstrap classloader, lib/rt.jar  
  4.             System.out.println("String classloader:"+String.class.getClassLoader());  
  5.   
  6.             // extclassloader lib/ext/*.jar  
  7.             Class<?> objClass = Class.forName("com.sun.crypto.provider.AESKeyGenerator");  
  8.             ClassLoader loader = objClass.getClassLoader();  
  9.             while (loader != null) {  
  10.                 System.out.println("AESKeyGenerator class load:" + loader.getClass().getName());  
  11.                 loader = loader.getParent();  
  12.             }  
  13.   
  14.             // AppClasssLoader ,classpath找  
  15.             objClass = Class.forName("Hello");  
  16.             loader = objClass.getClassLoader();  
  17.             while (loader != null) {  
  18.                 System.out.println("ABC classload:"+ loader.getClass().getName());  
  19.                 loader = loader.getParent();  
  20.             }  
  21.   
  22.         } catch (Exception e) {  
  23.             // TODO Auto-generated catch block  
  24.             e.printStackTrace();  
  25.         }  

String classloader:null

AESKeyGenerator class load:sun.misc.Launcher$ExtClassLoader

ABC classload:sun.misc.Launcher$AppClassLoader

ABC classload:sun.misc.Launcher$ExtClassLoader


String类是Java核心类,该类是由bootstarp classloader加载,而它是有c语言实现Java类中不可访问,所以这里输入null。 AESKeyGenerator类是ext下的一个类,可以该类是由ext classloader加载,它的父加载器为bootstarp,而自定义类Hello则使用了appclassloader来加载,而applcassloader的附加在其是extclassloder,有双亲委派可以知道加载hello类时候,appclassloader委托了extclassloader去加载,而extclassloader委托给bootstarp加载,bootstarp找不到该类,所以返回到extclassloader去ext 路径查找,查找不到有返回为appclassloader查找,appclasloader则在classpath中找到了该类,装入虚拟器运行。在这里我们可以在验证一下双亲委派模型的安全性,我们可以自己写一个String类,包路径和rt.jar里面的一模一样,看看程序是否会加载我们自己写的String类。
[java]  view plain  copy
  1. package java.lang;  
  2.   
  3. public class String {  
  4.        
  5. }  
[java]  view plain  copy
  1. package Demo.Demo;  
  2.   
  3. public class MyString {  
  4.        
  5. }  

测试类
[java]  view plain  copy
  1. public class Test {  
  2.   
  3.     @org.junit.Test  
  4.     public void test() throws ClassNotFoundException {  
  5.   
  6.         String str = new String();  
  7.         System.out.println("String classloader:" + str.getClass().getClassLoader());  
  8.           
  9.         MyString myStr = new MyString();  
  10.         System.out.println("MyString classloader:" +myStr.getClass().getClassLoader());  
  11.     }  
  12. }  

String classloader:null

MyString classloader:sun.misc.Launcher$AppClassLoader@36c51089


可见程序加载的String类是rt包下的String,因为加载它的classloader为null,而不是 AppClassLoader,这也证明了双亲委派模型是安全的,因为它避免了用户类覆盖Java核心类,造成系统的不安全。

五、从源码看classloader双亲委派模型

 protected Class<?> loadClass(Stringname,boolean resolve)

        throws ClassNotFoundException

    {

        synchronized (getClassLoadingLock(name)) {

            // First, check if the class has already been loaded

            Class c = findLoadedClass(name);

            if (c ==null) {

                longt0 = System.nanoTime();

                try {

                    if (parent !=null) {

                        c = parent.loadClass(name,false);

                    } else {

                        c = findBootstrapClassOrNull(name);

                    }

                } catch (ClassNotFoundExceptione) {

                    // 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.

                    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;

        }

    }

(1)首先看jvm缓存里面是否有该类,有的话直接返回,否者执行(2)
(2)看当前类加载器是否有父加载器,有的话执行(2),没有的话调用(3),
  (3)   bootstarp去加载,如果bootstrap找到则返回,否者执行(4)
  (4)   依次递归返回子加载器去看能否找到,找到直接返回,否者执行(5)
 (5) 在本地classpath查找,找到返回,否者ClassNotFoundException异常

  看到这里我们知道可以通过覆盖ClassLoader的findClass方法或者覆盖loadClass方法来实现自己的载入策略。
 
类加载器的顺序是:
先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值