阅读提示:
全文认真阅读大约需要1个半小时时间,如果你需要在IDE中验证并理解,大约需要3个小时,如果你想自己写个类似的类加载器并调试,估计还需要3个小时。
该知识点的掌握检测与否,你可以尝试其回答Java每日一题(9) 的问题。 
Point One
 
将J2EE应用程序移植到WebSphere应用程序服务器
http://www.ibm.com/developerworks/cn/websphere/library/techarticles/0408_baigang/part2.html
 
 
Point Two
类加载器的种类:
  1. Bootstrap ClassLoader/启动类加载器
    主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
  2. Extension ClassLoader/扩展类加载器
    主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。
  3. System ClassLoader/系统类加载器
    主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。
  4. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
    在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
类加载器的特性:
  1. 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
  2. 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构。

classloader-architecture

classloader-class-diagram

类图中,BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。因为,它已经完全不用java实现了。它是在jvm启动时, 就被构造起来的, 负责java平台核心库。
自定义类加载器加载一个类的步骤

classloader-load-class

ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:
// 检查类是否已被装载过        
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);        
         }        
}

线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader)。
// Now create the class loader to use to launch the application        
try {        
        loader = AppClassLoader.getAppClassLoader(extcl);        
} catch (IOException e) {        
        throw new InternalError(        
"Could not create application class loader" );        
}        
    
// Also set the context class loader for the primordial thread.        
Thread.currentThread().setContextClassLoader(loader);    

以上代码摘自sun.misc.Launch的无参构造函数Launch()。
使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).
线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使java类加载体系显得更灵活.
随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择。
当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException)。
为什么要使用这种双亲委托模式呢?
  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
java动态载入class的两种方式:
  1. implicit隐式,即利用实例化才载入的特性来动态载入class
  2. explicit显式方式,又分两种方式:
    1. java.lang.Class的forName()方法
    2. java.lang.ClassLoader的loadClass()方法
用Class.forName加载类
Class.forName使用的是被调用者的类加载器来加载类的。
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰。
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的。
public static Class forName(String className)        
         throws ClassNotFoundException {        
         return forName0(className, true , ClassLoader.getCallerClassLoader());        
}        
    
/** Called after security checks have been made. */    
private static native Class forName0(String name, boolean initialize,        
ClassLoader loader)        
         throws ClassNotFoundException;    

上面中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器
static块在什么时候执行?
  • 当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
  • 如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
  • static块仅执行一次
各个java类由哪些classLoader加载?
  • java类可以通过实例.getClass.getClassLoader()得知
  • 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()获得实例)载入
  • ClassLoader类由bootstrap loader载入
NoClassDefFoundError和ClassNotFoundException
  • NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
  • ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常
Point Three
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。

一.    ClassLoader基本概念
1.ClassLoader分类
类装载器是用来把类(class)装载进JVM的。
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)


JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。
AppClassLoaderParentExtClassLoader,而ExtClassLoaderParentBootstrap ClassLoader
 
Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader() 方法得到。
 
例1,测试你所使用的JVM的ClassLoader
/*LoaderSample1.java*/

public     class    LoaderSample1 {
         public     static     void    main(String[] args) {
                Class c;
                ClassLoader cl;
                cl    =    ClassLoader.getSystemClassLoader();
                System.out.println(cl);
                 while    (cl    !=     null ) {
                        cl    =    cl.getParent();
                        System.out.println(cl);
                }
                 try    {
                        c    =    Class.forName( " java.lang.Object " );
                        cl    =    c.getClassLoader();
                        System.out.println( " java.lang.Object's loader is    "     +    cl);
                        c    =    Class.forName( " LoaderSample1 " );
                        cl    =    c.getClassLoader();
                        System.out.println( " LoaderSample1's loader is    "     +    cl);
                }    catch    (Exception e) {
                        e.printStackTrace();
                }
        }
}
在我的机器上(Sun Java 1.4.2)的运行结果
sun.misc.Launcher$AppClassLoader@1a0c10f
sun.misc.Launcher$ExtClassLoader@e2eec8
null
java.lang.Object's loader is null
LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f
第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader
第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader
第三行表示,系统类装载器parent的parent为bootstrap
第四行表示,核心类java.lang.Object是由bootstrap装载的
第五行表示,用户类LoaderSample1是由系统类装载器装载的
 
 
二.parent delegation模型
从1.2版本开始,Java引入了双亲委托模型,从而更好的保证Java平台的安全。在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象,若parent不能装载,则由parent的请求者去装载

图 1 parent delegation模型
如图1所示,loader2的parent为loader1,loader1的parent为system class loader。假设loader2被要求装载类MyClass,在parent delegation模型下,loader2首先请求loader1代为装载,loader1再请求系统类装载器去装载MyClass。若系统装载器能成功装载,则将MyClass所对应的Class对象的reference返回给loader1,loader1再将reference返回给loader2,从而成功将类MyClass装载进虚拟机。若系统类装载器不能装载MyClass,loader1会尝试装载MyClass,若loader1也不能成功装载,loader2会尝试装载。若所有的parent及loader2本身都不能装载,则装载失败。
 
若有一个能成功装载,实际装载的类装载器被称为定义类装载器,所有能成功返回Class对象的装载器(包括定义类装载器)被称为初始类装载器。如图1所示,假设loader1实际装载了MyClass,则loader1为MyClass的定义类装载器,loader2和loader1为MyClass的初始类装载器。
 
需要指出的是,Class Loader是对象,它的父子关系和类的父子关系没有任何关系。
 
那么parent delegation模型为什么更安全了?因为在此模型下用户自定义的类装载器不可能装载应该由父亲装载器装载的可靠类,从而防止不可靠甚至恶意的代码代替由父亲装载器装载的可靠代码。实际上,类装载器的编写者可以自由选择不用把请求委托给parent,但正如上所说,会带来安全的问题。
 
 
三.命名空间及其作用
每个类装载器有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。
 
例2演示了一个命名空间的类如何使用另一命名空间的类。在例子中,LoaderSample2由系统类装载器装载,LoaderSample3由自定义的装载器loader负责装载,两个类不在同一命名空间,但LoaderSample2得到了LoaderSample3所对应的Class对象的reference,所以它可以访问LoaderSampl3中公共的成员(如age)。
例2不同命名空间的类的访问
/*LoaderSample2.java*/

import    java.net. * ;
import    java.lang.reflect. * ;
public     class    LoaderSample2 {
         public     static     void    main(String[] args) {
                 try    {
                        String path    =    System.getProperty( " user.dir " );
                        URL[] us    =    { new    URL( " file:// "     +    path    +     " /sub/ " )};
                        ClassLoader loader    =     new    URLClassLoader(us);
                        Class c    =    loader.loadClass( " LoaderSample3 " );
                        Object o    =    c.newInstance();
                        Field f    =    c.getField( " age " );
                         int    age    =    f.getInt(o);
                        System.out.println( " age is    "     +    age);
                }    catch    (Exception e) {
                        e.printStackTrace();
                }
        }
}

/*sub/Loadersample3.java*/

public     class    LoaderSample3 {
         static    {
                System.out.println( " LoaderSample3 loaded " );
        }
         public     int    age    =     30 ;
}
编译:javac LoaderSample2.java; javac sub/LoaderSample3.java
运行:java LoaderSample2
LoaderSample3 loaded
age is 30
从运行结果中可以看出,在类LoaderSample2中可以创建处于另一命名空间的类LoaderSample3中的对象并可以访问其公共成员age。
运行时包(runtime package)
由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。只有属于同一运行时包的类才能互相访问包可见的类和成员。这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类java.lang.Yes,并用用户自定义的类装载器装载,由于java.lang.Yes和核心类库java.lang.*由不同的装载器装载,它们属于不同的运行时包,所以java.lang.Yes不能访问核心类库java.lang中类的包可见的成员。
 
总结
命名空间并没有完全禁止属于不同空间的类的互相访问,双亲委托模型加强了Java的安全,运行时包增加了对包可见成员的保护。
 
二.    扩展ClassLoader方法
我们目的是从本地文件系统使用我们实现的类装载器装载一个类。为了创建自己的类装载器我们应该扩展ClassLoader类,这是一个抽象类。我们创建一个FileClassLoader extends ClassLoader。我们需要覆盖ClassLoader中的findClass(String name)方法,这个方法通过类的名字而得到一个Class对象。
public    Class findClass(String name)
        {
                 byte [] data    =    loadClassData(name);
                 return    defineClass(name, data,    0 , data.length);
        }

我们还应该提供一个方法loadClassData(String name),通过类的名称返回class文件的字
节数组。然后使用ClassLoader提供的defineClass()方法我们就可以返回Class对象了。

public     byte [] loadClassData(String name)
        {
                FileInputStream fis    =     null ;
                 byte [] data    =     null ;
                 try    
                {
                        fis    =     new    FileInputStream( new    File(drive    +    name    +    fileType));
                        ByteArrayOutputStream baos    =     new    ByteArrayOutputStream();
                         int    ch    =     0 ;
                         while    ((ch    =    fis.read())    !=     - 1 )
                        {
                                baos.write(ch);
                                
                        }
                        data    =    baos.toByteArray();
                }    catch    (IOException e)
                {
                        e.printStackTrace();
                }
                
                 return    data;
        }

转载于:https://blog.51cto.com/danni505/227437

ClassLoader知识收集相关推荐

  1. https ssl证书的工作原理及使用相关知识收集

    https ssl证书的工作原理及使用相关知识收集 SSL 与 数字证书 的基本概念和工作原理 前言 SSL是让人头大的东西,看起来很复杂,我学过信息安全课,但是对SSL仍然是模糊一片.对于数字证书也 ...

  2. Go问题/知识收集 - 1

    1.应用场景 主要用于收集Go的知识,临时存放. 2.学习/操作 1.文档阅读 source code https://github.com/bigwhite/publication 04|初窥门径: ...

  3. 数据挖掘 基础知识 收集

    A Tutorial on Clustering Algorithms-聚类小知识 俗话说:"物以类聚,人以群分",在自然科学和社会科学中,存在着大量的分类问题.所谓类,通俗地说, ...

  4. div实现返回符,倒三角,椭圆+小知识收集

    收集: 1,返回符(伪类元素): .back:before {content: "";width: .3rem;height: .3rem;border-left: .04rem ...

  5. 设计师 VR 成型设计知识收集

    关于核心的用户体验.用户界面与交互设计,分工:视觉.UI.UX.动效.用研.原型设计等.产品团队的目标依旧不变:创造更快的迭代周期,探索更多设计上的可能性 3D工具,在设计过程中,你很可能会遇到各种各 ...

  6. 关于DDD领域驱动设计的理论知识收集汇总

    最近一直在学习领域驱动设计(DDD)的理论知识,从网上搜集了一些个人认为比较有价值的东西,贴出来和大家分享一下: 我一直觉得不要盲目相信权威,比如不能一谈起领域驱动设计,就一定认为国外的那个Eric ...

  7. 硬件知识收集总结---20210811

    常用的硬件知识总结 1.CPU 定义:中央处理器,简称 CPU(Central Processing Unit),中央处理器主要包括两个部分,即控制器.运算器,其中还包括高速缓冲存储器及实现它们之间联 ...

  8. ionic3相关知识收集

    启动相关 1. 修改端口以启动多个项目 方法:使用ionic serve启动时,默认端口号为8100,修改这个端口号即可命令:ionic serve -p 8200 功能相关 1. 二维码功能 1.1 ...

  9. 手机浏览器页面知识收集

    wap手机网页css(wap css) 大部分手机现在都支持XHTML浏览器,这意味着我们只需在stylesheets的类型设置成"handheld"即 * { position: ...

最新文章

  1. 【分块】#6281. 数列分块入门 5(区间开方,区间求和)
  2. 超越ReLU却鲜为人知,3年后被挖掘:BERT、GPT-2等都在用的激活函数
  3. python实现http下载文件-Python实现HTTP协议下的文件下载方法总结
  4. 初等数论--同余方程--二元一次不定方程的通解形式
  5. C#3.0学习笔记(8)浅谈接口interface
  6. 服务器母鸡系统,母鸡云服务器
  7. linux mysql提交_MySQL 事务提交过程
  8. Flink 1.8.0中的状态生存时间特性:如何自动清理应用程序的状态
  9. 另外一款超棒的响应式布局jQuery插件 – Freetile.js
  10. Python random模块常用方法的使用
  11. 【收藏】NLP技术学习路线图,值得收藏,附下载
  12. paip.php页面调试设置及流程
  13. 【编译原理】第三章语法分析
  14. 插值法(三次样条插值)
  15. 软件测试及标准(基于ISO/IEC/IEEE 29119系列)
  16. littleVGL学习笔记9——lv_btn 按钮
  17. 查看win11激活状态
  18. HNSWNSG-基于图的ANN近邻搜索
  19. Linux安装kermit
  20. Ambari——大数据平台的搭建利器(一)

热门文章

  1. Rancher 2.2.2 发布,优化 Kubernetes 集群运维
  2. 你猜猜typeof (typeof 1) 会返回什么值(类型)?!
  3. iOS之runtime详解api(三)
  4. 物联网在“最后一公里”投递中的应用
  5. android网络编程之HttpUrlConnection的讲解--GET请求
  6. CorelDRAW快捷键搜集
  7. 使用WinPcap和libpcap类库读写pcap文件(001)开发环境配置
  8. linux 565显示格式,RGB565转BMP格式 C语言程序
  9. 速率单位和信息量单位区分
  10. 零基础参加java培训主要学什么