JVM系列之四:类装载器

class装载验证流程

加载

– 装载类的第一个阶段

– 取得类的二进制流

– 转为方法区数据结构

– Java堆中生成对应的java.lang.Class对象

链接

– 验证

· 目的:保证Class流的格式是正确的

· 文件格式的验证

· 是否以0xCAFEBABE开头

· 版本号是否合理

· 元数据验证

· 是否有父类

· 继承了final类?

· 非抽象类实现了所有的抽象方法

· 字节码验证 (很复杂)

· 运行检查

· 栈数据类型和操作码数据参数吻合

· 跳转指令指定到合理的位置

· 符号引用验证

· 常量池中描述类是否存在

· 访问的方法或字段是否存在且有足够的权限

 

– 准备

· 分配内存,并为类设置初始值 (方法区中)

· public static int v=1;

· 在准备阶段中,v会被设置为0

· 在初始化的<clinit>中才会被设置为1

· 对于static final类型,在准备阶段就会被赋上正确的值

· public static final  int v=1;

 

– 解析

· 符号引用替换为直接引用

初始化

– static变量 赋值语句

– 执行类构造器<clinit>

· static变量 赋值语句

· static{}语句

– 子类的<clinit>调用前保证父类的<clinit>被调用

– <clinit>是线程安全的

 

什么是类装载器ClassLoader

 

ClassLoader是一个抽象类

ClassLoader的实例将读入Java字节码将类装载到JVM

ClassLoader可以定制,满足不同的字节码流获取方式

ClassLoader负责类装载过程中的加载阶段

 

JDKClassLoader默认设计模式

ClassLoader的重要方法

– public Class<?> loadClass(String name) throws ClassNotFoundException

• 载入并返回一个Class

– protected final Class<?> defineClass(byte[] b, int off, int len)

• 定义一个类,不公开调用

– protected Class<?> findClass(String name) throws ClassNotFoundException

• loadClass回调该方法,自定义ClassLoader的推荐做法

– protected final Class<?> findLoadedClass(String name)

• 寻找已经加载的类

分类

– BootStrap ClassLoader (启动ClassLoader

• 只加载rt.jar中的类或者通过-Xbootclasspath设置该路径下的所有类

– Extension ClassLoader (扩展ClassLoader

• 只加载ext里面的所有类

– App ClassLoader (应用ClassLoader/系统ClassLoader

• 加载ClassPath下的所有类,也就是自己编写的类

– Custom ClassLoader(自定义ClassLoader)

每个ClassLoader都有一个Parent作为父亲

协同工作

 

双亲委派模式

使用双亲委派模型来组织类加载器之间的关系,好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。

例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。

相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,用户编写了一个java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但是永远无法被加载运行

双亲委派模型的系统实现:

双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却很简单,实现集中在java.lang.ClassLoaderloadClass() 
方法中,在其方法中,主要判断逻辑如下:先检查是否已经被加载过,

· 若没有被加载过,则接着判断父加载器是否为空。 

若不为空,则调用父类加载器loadClass()方法。

若父加载器为空,则默认使用启动类加载器作为父加载器

· 如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

 

 

问题

 

 

解决

– 上下文加载器

– 是一个角色

– 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题

– 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

– 示例

[java]  view plain  copy
  1. static private Class getProviderClass(String className, ClassLoader cl,  
  2.   
  3.         boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException  
  4.   
  5. {  
  6.   
  7.     try {  
  8.   
  9.         if (cl == null) {  
  10.   
  11.             if (useBSClsLoader) {  
  12.   
  13.                 return Class.forName(className, true, FactoryFinder.class.getClassLoader());  
  14.   
  15.             } else {  
  16.   
  17.                 cl = ss.getContextClassLoader();  
  18.   
  19.                 if (cl == null) {  
  20.   
  21.                     throw new ClassNotFoundException();  
  22.   
  23.                 }  
  24.   
  25.                 else {  
  26.   
  27.                     return cl.loadClass(className); //使用上下文ClassLoader  
  28.   
  29.                 }  
  30.   
  31.             }  
  32.   
  33.         }  
  34.   
  35.         else {  
  36.   
  37.             return cl.loadClass(className);  
  38.   
  39.         }  
  40.   
  41.     }  
  42.   
  43.     catch (ClassNotFoundException e1) {  
  44.   
  45.         if (doFallback) {  
  46.   
  47.             // Use current class loader - should always be bootstrap CL  
  48.   
  49.             return Class.forName(className, true, FactoryFinder.class.getClassLoader());  
  50.   
  51.         }  
  52.   
  53. …..  

代码来自于javax.xml.parsers.FactoryFinder,展示如何在启动类加载器加载AppLoader的类。

上下文ClassLoader可以突破双亲模式的局限性。

 

打破常规模式

双亲模式的破坏

– 双亲模式是默认的模式,但不是必须这么做

– TomcatWebappClassLoader 就会先加载自己的Class,找不到再委托parent

– OSGiClassLoader形成网状结构,根据需要自由加载Class

猜你喜欢

转载自blog.csdn.net/qq_21508727/article/details/80622410
今日推荐