Java虚拟机的类载入机制

Java虚拟机类载入过程是把Class类文件载入到内存。并对Class文件里的数据进行校验、转换解析和初始化,终于形成能够被虚拟机直接使用的java类型的过程。

在载入阶段,java虚拟机须要完毕下面3件事:

a.通过一个类的全限定名来获取定义此类的二进制字节流。

b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的执行时数据结构。

c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的訪问入口。

Java虚拟机的类载入是通过类载入器实现的, Java中的类载入器体系结构例如以下:

(1).BootStrap ClassLoader:启动类载入器。负责载入存放在%JAVA_HOME%\lib文件夹中的,或者通被-Xbootclasspath參数所指定的路径中的。而且被java虚拟机识别的(仅依照文件名称识别。如rt.jar,名字不符合的类库,即使放在指定路径中也不会被载入)类库到虚拟机的内存中,启动类载入器无法被java程序直接引用。

(2).Extension ClassLoader:扩展类载入器,由sun.misc.Launcher$ExtClassLoader实现,负责载入%JAVA_HOME%\lib\ext文件夹中的。或者被java.ext.dirs系统变量所指定的路径中的全部类库,开发人员能够直接使用扩展类载入器。

(3).Application ClassLoader:应用程序类载入器,由sun.misc.Launcher$AppClassLoader实现。负责载入用户类路径classpath上所指定的类库。是类载入器ClassLoader中的getSystemClassLoader()方法的返回值,开发人员能够直接使用应用程序类载入器,假设程序中没有自己定义过类载入器,该载入器就是程序中默认的类载入器。

注意:上述三个JDK提供的类载入器尽管是父子类载入器关系,可是没有使用继承,而是使用了组合关系。

从JDK1.2開始。java虚拟机规范推荐开发人员使用双亲委派模式(ParentsDelegation Model)进行类载入,其载入步骤例如以下:

(1).假设一个类载入器收到了类载入请求,它首先不会自己去尝试载入这个类,而是把类载入请求委派给父类载入器去完毕。

(2).每一层的类载入器都把类载入请求委派给父类载入器,直到全部的类载入请求都应该传递给顶层的启动类载入器。

(3).假设顶层的启动类载入器无法完毕载入请求,子类载入器尝试去载入,假设连最初发起类载入请求的类载入器也无法完毕载入请求时,将会抛出ClassNotFoundException。而不再调用其子类载入器去进行类载入。

双亲委派 模式的类载入机制的长处是java类它的类载入器一起具备了一种带优先级的层次关系。越是基础的类。越是被上层的类载入器进行载入,保证了java程序的稳定执行。双亲委派模式的实现:

[java] view plaincopyprint?

  1. protected synchronized Class<?

    > loadClass(String name, Boolean resolve) throws ClassNotFoundException{
  2. //首先检查请求的类是否已经被载入过
  3. Class c = findLoadedClass(name);
  4. if(c == null){
  5. try{
  6. if(parent != null){//委派父类载入器载入
  7. c = parent.loadClass(name, false);
  8. }
  9. else{//委派启动类载入器载入
  10. c = findBootstrapClassOrNull(name);
  11. }
  12. }catch(ClassNotFoundException e){
  13. //父类载入器无法完毕类载入请求
  14. }
  15. if(c == null){//本身类载入器进行类载入
  16. c = findClass(name);
  17. }
  18. }
  19. if(resolve){
  20. resolveClass(c);
  21. }
  22. return c;
  23. }
protected synchronized Class<?

> loadClass(String name, Boolean resolve) throws ClassNotFoundException{
	//首先检查请求的类是否已经被载入过
	Class c = findLoadedClass(name);
	if(c == null){
	try{
		if(parent != null){//委派父类载入器载入
	c = parent.loadClass(name, false);
}
else{//委派启动类载入器载入
	c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
	//父类载入器无法完毕类载入请求
}
if(c == null){//本身类载入器进行类载入
	c = findClass(name);
}
}
if(resolve){
	resolveClass(c);
}
return c;
}

若要实现自己定义类载入器,仅仅须要继承java.lang.ClassLoader 类。而且重写其findClass()方法就可以。java.lang.ClassLoader 类的基本职责就是依据一个指定的类的名称,找到或者生成其相应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外,ClassLoader 还负责载入 Java 应用所需的资源,如图像文件和配置文件等。ClassLoader中与载入类相关的方法例如以下:


方法


说明


getParent()


返回该类载入器的父类载入器。


loadClass(String name)


载入名称为 二进制名称为name 的类。返回的结果是 java.lang.Class 类的实例。


findClass(String name)


查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。


findLoadedClass(String name)


查找名称为 name 的已经被载入过的类。返回的结果是 java.lang.Class 类的实例。


resolveClass(Class<?> c)


链接指定的 Java 类。

注意:在JDK1.2之前。类载入尚未引入双亲委派模式,因此实现自己定义类载入器时经常重写loadClass方法,提供双亲委派逻辑。从JDK1.2之后,双亲委派模式已经被引入到类载入体系中。自己定义类载入器时不须要在自己写双亲委派的逻辑,因此不鼓舞重写loadClass方法,而推荐重写findClass方法。

在Java中。随意一个类都须要由载入它的类载入器和这个类本身一同确定其在java虚拟机中的唯一性,即比較两个类是否相等。仅仅有在这两个类是由同一个类载入器载入的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,仅仅要载入它的类载入器不同样,那么这两个类必然不相等(这里的相等包含代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceofkeyword的结果)。

样例代码例如以下:

[java] view plaincopyprint?

  1. package com.test;
  2. public class ClassLoaderTest {
  3. public static void main(String[] args)throws Exception{
  4. //匿名内部类实现自己定义类载入器
  5. ClassLoader myClassLoader = new ClassLoader(){
  6. protected Class<?> findClass(String name)throws ClassNotFoundException{
  7. //获取类文件名称
  8. String filename = name.substring(name.lastIndexOf(“.”) + 1) + “.class”;
  9. InputStream in = getClass().getResourceAsStream(filename);
  10. if(in == null){
  11. throw RuntimeException(“Could not found class file:” + filename);
  12. }
  13. byte[] b = new byte[in.available()];
  14. return defineClass(name, b, 0, b.length);
  15. }catch(IOException e){
  16. throw new ClassNotFoundException(name);
  17. }
  18. };
  19. Object obj = myClassLoader.loadClass(“com.test.ClassLoaderTest”).newInstance();
  20. System.out.println(obj.getClass());
  21. System.out.println(obj instanceof com.test. ClassLoaderTest);
  22. }
  23. }
package com.test;

public class ClassLoaderTest {
	public static void main(String[] args)throws Exception{
		//匿名内部类实现自己定义类载入器
	ClassLoader myClassLoader = new ClassLoader(){
	protected Class<?

> findClass(String name)throws ClassNotFoundException{
		//获取类文件名称
	String filename = name.substring(name.lastIndexOf(“.”) + 1) + “.class”;
	InputStream in = getClass().getResourceAsStream(filename);
	if(in == null){
	throw RuntimeException(“Could not found class file:” + filename);
}
byte[] b = new byte[in.available()];
return defineClass(name, b, 0, b.length);
}catch(IOException e){
	throw new ClassNotFoundException(name);
}
};
Object obj = myClassLoader.loadClass(“com.test.ClassLoaderTest”).newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.test. ClassLoaderTest);
}
}

输出结果例如以下:

com.test.ClassLoaderTest

false

之所以instanceof会返回false。是由于com.test.ClassLoaderTest类默认使用Application ClassLoader载入,而obj是通过自己定义类载入器载入的。类载入不同样,因此不相等。

类载入器双亲委派模型是从JDK1.2以后引入的。而且仅仅是一种推荐的模型。不是强制要求的,因此有一些没有遵循双亲委派模型的特例:

(1).在JDK1.2之前,自己定义类载入器都要覆盖loadClass方法去实现载入类的功能,JDK1.2引入双亲委派模型之后,loadClass方法用于委派父类载入器进行类载入。仅仅有父类载入器无法完毕类载入请求时才调用自己的findClass方法进行类载入,因此在JDK1.2之前的类载入的loadClass方法没有遵循双亲委派模型。因此在JDK1.2之后。自己定义类载入器不推荐覆盖loadClass方法。而仅仅须要覆盖findClass方法就可以。

(2).双亲委派模式非常好地攻克了各个类载入器的基础类统一问题,越基础的类由越上层的类载入器进行载入,可是这个基础类统一有一个不足,当基础类想要调用回下层的用户代码时无法委派子类载入器进行类载入。为了解决问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法能够设置线程上下文类载入器。

JavaEE仅仅是一个规范,sun公司仅仅给出了接口规范,详细的实现由各个厂商进行实现,因此JNDI。JDBC,JAXB等这些第三方的实现库就能够被JDK的类库所调用。

线程上下文类载入器也没有遵循双亲委派模型。

(3).近年来的热码替换,模块热部署等应用要求不用重新启动java虚拟机就能够实现代码模块的即插即用。催生了OSGi技术。在OSGi中类载入器体系被发展为网状结构。OSGi也没有全然遵循双亲委派模型。

时间: 2024-10-12 23:54:30

Java虚拟机的类载入机制的相关文章

深入java虚拟机学习 -- 类的加载机制(续)

昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: class Singleton { private static Singleton singleton = new Singleton(); //第一份代码的位置 public static int counter1; public static int counter2=0; private s

DexClassLoader和PathClassLoader类载入机制

0x00 在DexClassLoader和PathClassLoader载入Dex流程一文中,我们分析了dex文件怎样形成了DexFile结构体.本文中解说类载入机制,实际上就是生成ClassObject对象. 我们以DexClassLoader为例.解说类载入机制,PathClassLoader是一样的. 我们在载入类时一般会调用loadClass,那么我们就从loadClass来開始分析. 0x01 DexClassLoader类没有loadClass方法.所以调用的是父类ClassLoad

深入理解Java虚拟机(类文件结构)

深入理解Java虚拟机(类文件结构) 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_PRIVATE.各种字节码指令等等许多概念听起来都是云山雾罩.一知半解,原因就在于对类文件结构和类加载机制不够了解.直到后来细读了<深入理解 Java 虚拟机>中虚拟机执行子系统的相关内容,才建立了清晰的认知.如果你也和我一样,不了解类结构和类加载,但是工作中又涉及到字节码相关内容,相信后面两篇文章

深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

周志明的<深入理解Java虚拟机>很好很强大,阅读起来颇有点费劲,尤其是当你跟随作者的思路一直探究下去,开始会让你弄不清方向,难免有些你说的啥子的感觉.但知识不得不学,于是天天看,反复看,就慢慢的理解了.我其实不想说这种硬磨的方法有多好,我甚至不推荐,我建议大家阅读这本书时,由浅入深,有舍有得,先从宏观去理解去阅读,再慢慢深入,有条不紊的看下去.具体来说,当你看书的某一部分时,先看这部分的章节名,了解这部分这一章在讲什么,然后再看某一章,我拿"类文件结构"这一章来说,我必须

Java虚拟机 - Class类文件结构

[深入Java虚拟机]之二:Class类文件结构 平台无关性 Java是与平台无关的语言,这得益于Java源代码编译后生成的存储字节码的文件,即Class文件,以及Java虚拟机的实现.不仅使用Java编译器可以把Java代码编译成存储字节码的Class文件,使用JRuby等其他语言的编译器也可以把程序代码编译成Class文件,虚拟机并不关心Class的来源是什么语言,只要它符合一定的结构,就可以在Java中运行.Java语言中的各种变量.关键字和运算符的语义最终都是由多条字节码命令组合而成的,

【深入理解Java虚拟机】垃圾回收机制

本文内容来源于<深入理解Java虚拟机>一书,非常推荐大家去看一下这本书. 本系列其他文章: [深入理解Java虚拟机]Java内存区域模型.对象创建过程.常见OOM 1.垃圾回收要解决的问题 垃圾收集(Garbage Collection,GC),要设计一个GC,需要考虑解决下面三件事情: (1)哪些内存需要回收? (2)什么时候回收? (3)如何回收? 哪些内存需要回收? 根据<Java内存区域模型.对象创建过程.常见OOM>中介绍的java内存模型,其中,程序计数器.虚拟机栈

【深入理解Java虚拟机 】类的加载器

1. 类加载器的分类 JVM 自带的类加载器 根类加载器( BootStrap ) 拓展类加载器 ( Extension ) 系统 (应用) 加载器 ( System / AppClassLoader) 开发者自己创建的类加载器 java.long.ClassLoader 的子类 public abstract class ClassLoader { // 抽象类,不能实例化吗,需要继承并重写其方法 } 2. 加载时机 类加载器不是在 "首次主动" 使用的时候采取尝试加载一般情况下回提

Java虚拟机——Class类文件结构

Class文件格式采用一种类似C语言结构体的结构来存储数据,这种数据结构只有两种数据类型:无符号数和表.      无符号数属于基本的数据类型,数据项的不同长度分别用u1, u2, u4, u8表示, 分别表示一种数据项在class文件中占据一个字节, 两个字节, 4个字节和8个字节.      表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表习惯的以“_info”作为结尾,可以说,整个Class文件就是一个表结构! 下表列出了Class文件中各个数据项的具体含义: 类型 名称 数

java虚拟机装载类的步骤

类装载步骤:在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载.链接和初始化,其中链接又可以分成校验.准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下: 装载:查找和导入类或接口的二进制数据: 链接:执行下面的校验.准备和解析步骤,其中解析步骤是可以选择的: 校验:检查导入类或接口的二进制数据的正确性: 准备:给类的静态变量分配并初始化存储空间: 解析:将符号引用转成直接引用: 初始化:激活类的静态变量的初始化Java代码和静态Java