类加载过程,双亲委派模型?

背景

java通过字节码和JVM机制,提供了强大的跨平台能力,理解Java的类加载机制能让我们更加了解java的运行过程

为什么要进行类加载?

  1. 我们所写的java代码是我们人能看懂的,但是计算机并不认识它,所以我们就得把它进行转换,首先第一步就是得将我们所编写的 Java文件编译为class文件,然后通过我们的类加载过程将.class文件加载到内存且转换为计算机可以认识的语言。

类加载过程?

  1. 类加在过程分为三个大阶段和五个小阶段。类加载过程,双亲委派模型?_第1张图片

加载阶段(Loading

它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象),这里的数据源可能是各种各样的形态,如 jar 文件、class 文件,甚至是网络数据源等;如果输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。

(1)文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理。这里面主要对魔数、主版本号、常量池等等的校验(魔数、主版本号都是.class文件里面包含的数据信息、在这里可以不用理解)。

(2)元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。

(3)字节码验证:这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出威海虚拟机安全的事。

(4)符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外的信息进行校验。目的是确保解析动作能够完成。
对整个类加载机制而言,验证阶段是一个很重要但是非必需的阶段,如果我们的代码能够确保没有问题,那么我们就没有必要去验证,毕竟验证需要花费一定的的时间。当然我们可以使用-Xverfity:none来关闭大部分的验证。

链接(Linking)

这是核心的步骤,简单说是把原始的类定义信息平滑地转化入 JVM 运行的过程中。这里可进一步细分为三个步骤:

验证(Verification)

这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规的信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。

准备(Preparation)

创建类或接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面
的显式初始化阶段是有区别的,可以理解为半初始化状态,
侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令,比如

public static int value = 1;

在这里准备阶段过后的value值为0,而不是1。赋值为1的动作在初始化阶段。
当然还有其他的默认值。
注意,在上面value是被static所修饰的准备阶段之后是0,但是如果同时被final和static修饰准备阶段之后就是1了。我们可以理解为static final在编译器就将结果放入调用它的类的常量池中了。

解析(Resolution)

在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。

(1)符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好,就好比在班级中,老师可以用张三来代表你,也可以用你的学号来代表你,但无论任何方式这些都只是一个代号(符号),这个代号指向你(符号引用)

(2)直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

初始化阶段(initialization)

这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。

什么是双亲委派模型?

理解双亲委派模型

  • 感觉这个字面意思好难理解, 个人理解:有一件事我不敢让父亲去干,但是我父亲默认也不干,于是让父亲的父亲去干,父亲的父亲也不干(也就是你爷爷),你爷爷也不干,让你爷爷的父亲去干于是你爷爷的父亲也没办法推辞的上面没人了。只好自己干了。但是有一种可能就是你爷爷的父亲干不了这件事,于是又往下推谁能干谁就干。
    类加载过程,双亲委派模型?_第2张图片

  • 上面的过程中是如何来确定自己无法加载呢?

    1)如上图,其中每个类加载气都有自己对应加载的目标,比如说我们的Object,它是存放在rt.jar之中的,无论哪个类加载器要加载这一个类,最终都会委派到最顶端的启动类加载器,因此Object类在程序的各种类加载器环境中都是同一个类,就算是你在classpath去写一个Object对象,到最后也会加载得失rt.jar中的Object对象。这也就体现了双亲委派要解决的问题之一,就是防止重复加载导致冲突。导致程序一片混乱。

四种类加载器的介绍

  • Bootstrap ClassLoader:启动类加载器,这个类加载器将负责存放在/lib目录中、被-Xbootclasspath参数所指定的路径中,并且是虚拟机会识别的jar类库加载到内存中。更直白点说,就是我们常用的java.lang开头的那些类,一定是被Bootstrap ClassLoader加载的。

  • Extension ClassLoader:扩展类加载器,这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载/lib/ext目录中的、或者被java.ext.dirs系统变量指定的路径中的所有类库。

  • Application ClassLoader:应用程序类加载器,这个类加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载用户CLASSPATH环境变量指定的路径中的所有类库。如果应用程序中没有自定义过自己的类加载器,这个就是一个Java程序中默认的类加载器。

  • 用户自定义的类加载器:用户在需要的情况下,可以实现自己的自定义类加载器,一般而言,在以下几种情况下需要自定义类加载器:

      (1)隔离加载类。某些框架为了实现中间件和应用程序的模块的隔离,就需要中间件和应用程序使用不同的类加载器;
                  
      (2)修改类加载的方式。类加载的双亲委派模型并不是强制的,用户可以根据需要在某个时间点动态加载类;
                  
      (3)扩展类加载源,例如从数据库、网络进行类加载;
                  
      (4)防止源代码泄露。Java代码很容易被反编译和篡改,为了防止源码泄露,可以对类的字节码文件进行加密,并编写自定义的类加载器来加载自己的应用程序的类。
    

你可能感兴趣的:(JVM,java)