Java类装载过程与类装载器

1. class装载验证流程

Java类装载过程与类装载器_第1张图片

1.1 加载

  • 装载类的第一个阶段
  • 取得类的二进制流
  • 转为方法区数据结构
  • 在Java堆中生成对应的java.lang.Class对象

1.2 链接 -> 验证

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

文件格式的验证

  • 是否以0xCAFEBABE开头
  • 版本号是否合理
元数据验证
  • 是否有父类
  • 继承了final类?
  • 非抽象类实现了所有的抽象方法
字节码验证 (很复杂)
  • 运行检查
  • 栈数据类型和操作码数据参数吻合
  • 跳转指令指定到合理的位置
符号引用验证
  • 常量池中描述类是否存在
  • 访问的方法或字段是否存在且有足够的权限

1.3 链接 -> 准备

分配内存,并为类设置初始值 (方法区中,关于方法区请查看Java内存区域)

  • public static int v=1;
  • 在准备阶段中,v会被设置为0
  • 在初始化的<clinit>中才会被设置为1
  • 对于static final类型(常量),在准备阶段就会被赋上正确的值
  • public static final  int v=1;

1.4 链接 -> 解析

符号引用替换为直接引用

符号引用只是一种表示的方式,比如某个类继承java.lang.object,在符号引用阶段,只会记录该类是继承"java.lang.object",以这种字符串的形式保存,但是不能保证该对象被记载。

直接引用就是真正能使用的引用,它是指针或者地址偏移量,引用对象一定在内存。最终知道在内存中到底放在哪里。

替换后,Class才能索引到它要用的那些内容。

1.5 初始化

执行类构造器<clinit>

  • static变量 赋值语句
  • static{}语句

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

实验:

package test;

public class Test extends A
{
	static {
		System.out.println("Test");
	}
	public static void main(String[] args)
	{
		A a = new A();
	}
}

class A {
	static {
		System.out.println("A");
	}
}
输出:
A
Test
<clinit>是线程安全的

那么Java.lang.NoSuchFieldError错误可能在什么阶段抛出呢?
很显然是在链接的验证阶段的符号引用验证时会抛出这个异常,或者NoSuchMethodError等异常。

2. 什么是类装载器ClassLoader

  • ClassLoader是一个抽象类
  • ClassLoader的实例将读入Java字节码将类装载到JVM中
  • ClassLoader可以定制,满足不同的字节码流获取方式(譬如从网络中加载,从文件中加载)
  • ClassLoader负责类装载过程中的加载阶段

2.1 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) 
寻找已经加载的类

2.2 系统中的ClassLoader

  • BootStrap ClassLoader (启动ClassLoader)
  • Extension ClassLoader (扩展ClassLoader)
  • App ClassLoader (应用ClassLoader/系统ClassLoader)
  • Custom ClassLoader(自定义ClassLoader)
每个ClassLoader都有一个Parent作为父亲( BootStrap除外)

3. JDK中ClassLoader默认设计模式

Java类装载过程与类装载器_第2张图片
自底向上检查类是否被加载,一般情况下,首先先从AppClassLoader中调用findLoadedClass查看是否已经加载,如果没有加载,则会交给父类,ExtensionClassLoader去查看是否加载,还没加载,则再调用其父类,BootstrapClassLoader查看是否已经加载,如果仍然没有,自顶向下尝试加载类,那么从 BootstrapClassLoader到 AppClassLoader依次尝试加载。

从代码上可以很容易看出来,首先先自己查看这个类是否被调用,如果没有找到,则调用父亲的loadClass,直到BootstrapClassLoader(没有父亲)。

我们把这个加载的过程叫做双亲模式。

双亲委托机制的作用是防止系统jar包被本地替换

3.1 双亲模式的问题

这种模式下会有一个问题:

顶层ClassLoader,无法加载底层ClassLoader的类

Java框架(rt.jar)如何加载应用的类?

比如:javax.xml.parsers包中定义了xml解析的类接口
Service Provider Interface SPI 位于rt.jar 
即接口在启动ClassLoader中。
而SPI的实现类,在AppLoader。

这样就无法用BootstrapClassLoader去加载SPI的实现类。

3.2 解决

JDK中提供了一个方法:

Thread. setContextClassLoader()
  • 上下文加载器
  • 是一个角色
  • 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
  • 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

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

3.3 双亲模式的破坏

  • 双亲模式是默认的模式,但不是必须这么做
  • Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
  • OSGi的ClassLoader形成网状结构,根据需要自由加载Class

Reference:

1. http://www.cnblogs.com/snake-hand/p/3151381.html

2. http://www.cnblogs.com/Lawson/archive/2012/07/31/2616623.html

你可能感兴趣的:(ClassLoader,类装载器,双亲委派模式,装载验证过程,Java类装载过程)