Java虚拟机之类加载机制和双亲委派模型原理分析

定义:虚拟机将class文件加载到内存,并对数据进行校验,准备,解剖,初始化后转化为直接使用的Java类型,就是类的加载机制。

类的加载过程

加载——》验证——》准备——》解剖——》初始化——》使用——》卸载。

类加载的时机

类在虚拟机中不是虚拟机启动的时候就将所有的class文件都进行加载,而是是动态加载的。

五种出发类加载的场景     当且仅当

  1. 当遇到new ,getstatic,setstatic,invokestatic命令时,并且类之前没有被加载过。
  2. 类被反射调用的时候,类之前没有被加载过。
  3. 当加载一个类时,发现他的父类没有被加载,那么就先加载他的父类。
  4. 入口方法所在的类,如main方法所在的类,当虚拟机启动时就会被加载。
  5. 当时用JDK1.7动态语言支持时,对一个实例的解剖结果是REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,如果句柄所在的类未被加载过,就对这个类加载。

 加载

加载属于类加载的第一步

完成的三件事

  1. 根据全限定名,查找获取class文件。
  2. 将这个class文件的常量池中的数据转化为方法区常量池中的数据。
  3. 在内存中生成这个类的java.lang.Class对象,放入方法区。

 记载过程和验证的过程时交叉进行的,相辅相成,并且相互制约。

验证

验证虽然是类加载的第二步,但是在加类载的过程中的每一步就伴随着验证过程。

验证的阶段

  1. 文件格式的验证  这个阶段组要是围绕class文件进行的,会验证二进制字节码文件是否是class文件,是否符合虚拟机规范,只有经过着个验证通过之后,文件才会进入方法区。
  2. 元数据的验证   方法区数据结构阶段的验证,验证元数据信息的语义是否符合java的语义。
  3. 字节码验证  主要的目的是通过数据流和控制流的分析,确定语法是否合法,符合逻辑。
  4. 符号引用验证  在主要为了解剖阶段符号引用转化为直接引用时的验证  查看符号引用是否有相应的信息与之匹配。

准备 

准备阶段是为类变量(static修饰的变量)分配内存空间,并且赋初始值的阶段。这些变量都在方法区中分配空间。

 这里说的赋初值是赋数据类型的零值,例如 public static int value = 123;  ,这里赋值时velue赋值为0,因为int类型的零值是0。原因是因为此时为执行任何java代码,只是给类变量分配存储空间。注意:被static和final同时修饰的变量会在准备阶段赋制定初值,例如  public void static final int value = 123;  value就会赋值为123。

解剖

解剖阶段就是将符号引用转化为直接引用的阶段。

  • 直接引用:直接引用是直接指向对象的指针,字面值或者是指向能间接指向目标的句柄的指针,在内存中始终有一个目标与之匹配。存在于虚拟机中。
  • 符号引用:符号引用是用一组符号来描述所引用的目标,只起到描述引用目标的作用, 都存在与class文件中。 

初始化 

执行<clinit>()方法(clinit是class initialize的简写),<clinit>()方法再编译过程中生成,此方法中包含了所有类变量的赋值以及静态代码语句块的执行代码。

类加载器 

 Java团队将“通过类的全限定名来过去二进制class文件”交由虚拟机外部来实现,实现这个动作的模块就叫做类加载器。类加载器属于运行时环境的一部分,负责动态的将java类加载到java虚拟机的内存空间中,是一种按需加载,即当第一次使用该类时才加载。

对于任何一个类,都需要加载他的类加载器和类本身才能确定类在虚拟机的唯一性,也即是说同一个类被不同的类加载器加载,那么这两个类不相等。

双亲委派模型

从虚拟机的角度堆类加载器进行分类

1,启动类加载BootStrap ClassLoader  ,它是虚拟机内部的加载器,是C/C++语言编写的。

2,其他加载器,是虚拟机外部的加载器,由java语言开发, 其他加载器属于java里面的类,也是由BootStrap ClassLoader加载的。

从开发人员的角度对加载器进行分类

1,启动类加载器(BootStrap Classloader),随着java虚拟机的启动而启动,这个类加载器负责加载<JAVA_HOME>\lib下的类库,并且这些jar包必须是被虚拟机识别的类库(BootStrap Classloader加载器只对自己认识的文件名的jar包进行加载,不认识的名字的jar包即使放在lib文件夹中也不能被加载),BootStrap Classloader加载器不能直接被程序直接引用,因为不是java语言写的类。

2,扩展类加载器(Extension ClassLoader),由Java语言编写,也是一个类,这个加载器负责记载<JAVA_HOME>\lib\ext下的类库,开发者可以直接使用该加载器。

3,应用程序类加载器Appication ClassLoader,由Java语言编写,也是一个类,负责加载用户ClassPath下的类,也是程序默认的加载器。

我们的程序运行中类的加载是由这几个加载器结合完成的,在加载器工作中的模型为:

双亲委派模型要求除了BootStrap ClassLoader 类加载器没有直接的父类加载器之外,都应该有自己的父类加载器,不是以继承关系工作,而是以组合的形式工作。

双亲委派模型工作流程

当一个类加载器接收到类加载请求的时候比如上图的User ClassLoader加载器,他不会直接去加载这个类,他会首先查看自己的缓存区域内有没有这个类,没有就向父类加载器Application ClassLoader委派,Application ClassLoader拿到请求之后也是先查看自己的缓存,如果没有就委派给他的父类加载器Extension Classloader,Extension ClassLoader查看自己的缓存,没有直接委派给启动加载器BootStrap Classloader,BootStrap Classloader加载器缓存中也没有的话就尝试去加载类,加载到就返回这个类的实例,加载不到,就交由子加载器加载,子类加载不到就依次传递给子类加载,最终交由自己来加载这个类,能加载到放入内存,加载不到就报java.lang.ClassNotFoundException。

委派是由下到上的,最顶级的类加载器如果加载不到,加载开始由上到下在家。

看一下源码

protected Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
{
	synchronized (getClassLoadingLock(name)) {
		// First, check if the class has already been loaded
		//首先 检查类是否已经被加载
		Class<?> c = findLoadedClass(name);
		if (c == null) {
			long t0 = System.nanoTime();
			
			try {
				//加载器父类加载器不为空 委派父类加载器加载
				if (parent != null) {
					c = parent.loadClass(name, false);
				} else {
                //规定除了启动类加载器 都有父类加载器
				//没有父类加载器 使用启动类加载器加载
					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 orderto find the class.
				long t1 = 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);
		}
		return c;
	}
}
//检查类是否已经被加载的方法
protected final Class<?> findLoadedClass(String name) {
	if (!checkName(name))
		return null;
	//调用findLoadedClass0方法检查
	return findLoadedClass0(name);
}
//findLoadedClass0是一个native方法 追踪不到了
private native final Class<?> findLoadedClass0(String name);

//启动类加载器加载 并返回类
private Class<?> findBootstrapClassOrNull(String name)
{
	if (!checkName(name)) return null;

	return findBootstrapClass(name);
}

    // return null if not found
private native Class<?> findBootstrapClass(String name);

好处:

1.java类随着它的加载器一起具备了一种带有优先级的层次关系,能够避免类的重复加载,保证类在内存中的唯一性。

2. 保护了jdk的核心类库不受影响,例如用户自己定义了一个java.lang.Object,如果不使用双亲委派模型,由非启动类加载器加载了自定义java.lang.Object类,会使核心类库被篡改。

那么jvm是怎么判断一个类有没有已经加载过的呢?

当类加载器接到加载请求时,这里除了判断class文件是一致的之外,还需要判断是不是同一个类加载器加载的才行。比如App ClassLoader已经加载了一次Test类,当Test类再次加载请求时,除了要判断这次请求的Test类的Class文件与已经加载的Test类是否相同外,还要判断已经加载过的Test类的加载器和这次请求的类加载器是否相同。相同就是加载过,不相同就是没加载过,就开始采用双亲委派模型加载。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值