如何理解Java类加载机制

java 类加载机制 也就是class文件到虚拟机

在这里插入图片描述

加载

  • 通过一个类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
  • 注意: 这里并不一定非得从一个class文件获取,这里既可以从zip包中读取(war,jar)也可以在运行时计算生成(动态代理),也可以由其他文件生成。

链接

  1. 验证:为了确保类文件的字节流中包含的信息符合当前虚拟机的要求。
  • 文件格式验证
  • 字节码验证
  • 元数据验证
  • 引用符号验证
  1. 准备: 为类的静态变量分配内存并将其初始化默认值。

准备阶段是正式为类的静态变量分配内存,并设置类的静态变量的初始值阶段,在方法区中分配这些静态变量所使用的内存空间。
注意: 这里说的初始化值概念是:
比如 static int value= 88; 实际上value在准备阶段过后的初始化值为0,而不是88.
或者 static final int value = 88; 在编译阶段会为value生成ConstactValue属性,在准备阶段虚拟机会根据ConstactValue属性复制为88;

  1. 解析:指虚拟机将常量池中的符号引用替换成直接引用。

符号引用与虚拟机的内存布局无关,引用的目标并不一定要加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄,如果有了直接引用,那引用的目标已经在内存中存在了。

初始化

对类的静态变量,静态代码块执行初始化操作。

初始化阶段是类加载最后一个阶段,除了在加载可以自定义类的加载器以外,其他操作都是有JVM主导的,到了初始化阶段才真正执行类中定义的Java代码。
初始化阶段是执行类构造方法的过程,构造方法是由编译器自动完成类中的静态变量赋值和静态代码块执行的。虚拟机会保证构造方法执行之前,父类的构造方法已经执行完毕。
如果一个类中没有静态变量,也没有静态语句块,那么编译器可以不生成构造方法。

注意:以下几种情况不会执行类初始化

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  2. 定义了对象数组,不会触发类的初始化。
  3. 常量在编译期间会存入调用类的常量池中,本质上并么有直接引用定义常量的类,不会触发定义常量所在类的初始化。
  4. 通过类名获取class对象,不会触发类的初始化
  5. 通过class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类的初始化
  6. 通过ClassLoader默认的loadclass方法,也不会触发初始化。

类加载器 ClassLoader

在加载(Load)阶段,其中第(1)步:通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾
名思义,就是用来装载Class文件的。通过一个类的全限定名获取定义此类的二进制字节流

  1. Bootstrap ClassLoader 启动类加载器 负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或Xbootclassoath 选项指定的jar包。由C++实现,不是ClassLoader子类。
  2. Extension ClassLoader 扩展类加载器 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。
  3. App ClassLoader 应用程序类加载器 负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。
  4. Custom ClassLoader 自定义类加载器 通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要 自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。
    在这里插入图片描述
    类加载器的实现原理(双亲委派)
    当一个类加载器收到类加载任务时,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器。只有当父类加载器无法完成加载任务时,才会尝试当前类加载器去执行加载任务。
    检查某个类是否已经加载:顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加载,就视为已加载此类,保证此类只所有ClassLoader加载一次。
    加载的顺序:加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类
    双亲委派机制

定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任
务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加
载器无法完成此加载任务时,才自己去加载。
优势: Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,
它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器
进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载
器自己取加载的话,那么系统中会存在多种不同的Object类。
破坏: 重写loadClass()方法。线程上下文类加载器破坏了双亲委派模型的一般性原则。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章