类加载机制——过程篇

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5.  初始化

 

一,加载

类加载器(ClassLoader)

  当编辑器编译java源文件后,会产生以个相对一的字节码文件(.class) 

  当程序执行开始之前,必须将这个文件载入内存中,生成一个与之匹配的Class对象,

  任何以个类加载之后jvm都会为其创建以个唯一的class对象(元对象),再后续都是通过这个Class对象来创建实例,后话(Class对象就是放射的基石。)

  这个过程我们称之为类加载  

 

  要弄清楚类加载的机制,授信我们必须要清楚了解类加载的相关知识,它是完成整个类加载的重要工具。

  简单的说,当有个Class文件载入内存之后,并构建成一个Class对象的过程,就称之为类加载

类加载器(三类)

1,启动类加载器(BootStrap ClassLoader)

  启动类加载器是负责加载jdk核心包下的类文件,对应的目录就是“JDK安装目录/jre/lib”。其中最重要的以个jar文件就是rt.jar

  注意:这个类加载器我们是没有办法区操作的,因为它本身是用c++语言实现的

2,扩展类加载器(Extension ClassLoader)

  这个类加载器,是由启动类加载器来加载的,扩展类加载器是用java语言实现,它主要负责加

  载jdk里面的扩展jar文件,对应 目录是"/JDK安装目录/jre/lib/ext"

3,应用程序类加载器(Application ClassLoader)

  这个类加载器也是使用java语言实现,它同样由引导类加载器加载。它主要负责加载我们开发人员说明编写的java类,通常对应的目录就是环境变量下的ClassPath路径

 

双亲委托模型

  jvm的类加载时基于双亲委托模型,也就是当我们编写一个任意类的时候,本身不是由应用程序加载器立即加载,而是先委托给夫加载器,当夫加载器再自己说明加载的范围内找不到相对应的class文件时,最后才会将加载权交回给子加载器区加载,这样做的目的是为了保证jvm的安全机制

双亲委托模型的工作流程:

  如果以个类加载器收到类加载的请求,它首先不会自己先尝试加载这个类,而是把请求委托给父类加载器去完成,依次向上,因此,所有的类加载器请求最终都应该被传递到顶层的启动类的加载器中,只有当父加载器在它的搜索范围中没有找到所需要的类时,即无法完成该加载,子加载器才会尝试自己去加载类

 

二,验证

  验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求。

 

文件格式验证:

  验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,改验证的主要目的是保证俨如的字节流正确地解析并存储于方法区之内。经过该阶段的验证之后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。

 

元数据验证:

  对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合java语法规范的元数据信息。

 

字节码验证:

  该阶段验证的主要工作是进行数据流和控制流的分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不惠做出危害虚拟机安全的行为。

 

符号应用验证:

  这是最后一个验证阶段,它发生在虚拟机将符号引用转换为直接引用的时候(解析极端中发生该幻化,后面会有解释),主要对类自身以为的信息(常量池中的各种符号引用)进行匹配性的校验

 

三,准备

  准备阶段是正式的为变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配,对于该阶段有一下几点注意:

  1,内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一起分配在java堆中。

  2,设置的初始值通常情况下是数据类型的默认值(如 0,0L,null,false等),而不是被java代码中被显示地赋予值

 

假设一个类变量的定义为:

准备阶段初始值为0,

因为这时候尚未开始执行任何java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类初始化方法<clinit>中

方法<clinit>:

  java语言中间语言的一个类初始化方法,它是在初始化阶段才被jvm调用,所以把value赋值为3的动作将在初始化阶段才会执行。注意:并非所有的类都会拥有一个<clinit>方法,以下条件中该类不会拥有<clinit>方法:

  1,该类既没有声明任何类变量,野没有静态初始化语句;

  2,该类声明了类变量,但没有明确使用类变量初始化语句或静态初始化语句初始化;

  3,该类仅包含静态final变量的类变量初始化语句,并且类变量初始化语句是编译时常量表达式

 

下表列出了 Java 中所有基本数据类型以及 reference 类型的默认零值:

 

 

这里还需要注意如下几点:

  • 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。
  • 对于同时被 static 和 final 修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被 final 修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。
  • 对于引用数据类型 reference 来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
  • 如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。

假设上面的类变量 value 被定义为:0

public static final int value = 3;

编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为 3。

未完待续……

 

posted @ 2017-12-05 10:57  信息界的搬运工  阅读(11)  评论(0编辑  收藏  举报