一、什么是类加载?

  JVM将class字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class 对象,作为方法区类数据的访问入口。

二、类加载过程

  类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、链接(验证、准备、解析)、初始化、使用卸载七个阶段。它们开始的顺序如下图所示:

  

  2.1 加载

    将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类

    加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

    1. 通过一个类的全限定名来获取其定义的二进制字节流。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3. 在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。

    注意:第一条中的数据来源不单单指从class文件中获取,通常有如下几种来源:

    1. 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
    2. 从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
    3. 通过网络加载class文件。
    4. 把一个Java源文件动态编译,并执行加载

    相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

    加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在 Java 堆中也创建一个 java.lang.Class 类的对象,这样便可以通过该对象访问方法区中的这些数据。

    

  2.2 验证

   为了确保 Class 文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证

   文件格式验证:主要验证字节流是否符合Class文件格式规范(ca fe ba be),并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。

   元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。

  字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。

  符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
  

  2.3 准备

    准备阶段是正式为类变量分配内存并设置类变量默认值的阶段(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间

    注意:

    1. static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
    2. static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成赋值在初始化阶段完成
    3. 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
    4. 如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成

  2.4 解析

    将常量池中的符号引用转化为直接引用的过程。

    2.4.1 符号引用和直接引用:

      符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中,布局与内存无关

      直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。

    2.4.2 四种解析:

       1.类或接口的解析   2.字段解析类    3.方法解析   4.接口方法解析

      具体理解请见  http://wiki.jikexueyuan.com/project/java-vm/class-loading-mechanism.html

  2.5 初始化

    初始化可以说是类加载的最后一个阶段,到了此阶段,才真正开始执行类中定义的 Java 程序代码。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器<cinit>()V 方法的过程,虚拟机会保证这个类的『构造方法』的线程安全

    发生时机

    概括得说,类初始化是【懒惰的】

    1. main 方法所在的类,总会被首先初始化
    2. 首次访问这个类的静态变量或静态方法时
    3. 子类初始化,如果父类还没初始化,会引发
    4. 子类访问父类的静态变量,只会触发父类的初始化
    5. Class.forName new 会导致初始化

    不会导致类初始化的情况

    1. 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
    2. 类对象.class 不会触发初始化
    3. 创建该类的数组不会触发初始化

  

三、类加载器

  类加载器虽然只用于实现类的加载动作,但它在 Java 程序中起到的作用却远远不限于类的加载阶段。对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在就 Java 虚拟机中的唯一性,也就是说,即使两个类来源于同一个 Class 文件,只要加载它们的类加载器不同,那这两个类就必定不相等。这里的“相等”包括了代表类的 Class 对象的 equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用 instanceof 关键字对对象所属关系的判定结果。

  类加载器可分为以下四类:

  

  3.1  启动类加载器(Bootstrap ClassLoader)

  它使用 C++ 实现,负责加载存放在JDK\jre\lib(JDK 代表 JDK 的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的java.*开头的类均被 Bootstrap ClassLoader 加载)。启动类加载器是无法被 Java 程序直接引用的。

  

  3.2 拓展类加载器(Extension ClassLoader)

  该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

  3.3 应用程序类加载器:(Application ClassLoader)

  该类加载器由 sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

  3.4 用户自定义加载器 :(Customized Class Loader)

  用户可以自己定义类加载器来加载类。所有的类加载器都要继承 java.lang.ClassLoader 类并重写 findClass(String name) 方法。用户自定义类加载器默认父加载器是 应用程序加载器

四、类加载机制

  4.1 全盘负责委托机制

    当进行类加载的时候,如果手动指定了ClassLoader,那么该类所依赖和引用的类也由这个类加载器进行加载

    User->UserParent

    指定User使用特定的类加载器,那么跟User类有依赖和引用关系的类也用这个类加载器进行加载

  4.2 双亲委派机制

    4.2.1 工作原理:

    如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。

    4.2.2  优势:

     Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。例如,类java.lang.Object 类存放在JDK\jre\lib下的 rt.jar 之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了 Object 类在程序中的各种类加载器中都是同一个类。

    4.2.3 图解:

  

    4.2.4 源码:

 //提供class类的二进制名称表示,加载对应class,加载成功,则返回表示该类对应的Class<T> instance 实例
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查是否已经被当前的类加载器记载过了,如果已经被加载,直接返回对应的Class<T>实例
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 order
// to 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;
}
}

五、破坏双亲委派机制

  5.1 为什么要破坏双亲委派机制?

    在某些情况下父类加载器需要委托子类加载器去加载class文件

  5.2 怎么样破坏双亲委派机制?

    1.重写ClassLoad类中的loadClass方法

    2.手动调用系统类加载器   Thread.currentThread().getContextClassLoader();

    3.OSGi

    具体了解:https://www.cnblogs.com/fengtingxin/p/11872710.html   和    https://www.cnblogs.com/joemsu/p/9310226.html

六、总结  

  1.  根据JVM内存配置要求,为JVM申请特定大小的内存空间;

  2.  创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;

  3.   创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader;

  4.  使用上述获取的ClassLoader实例加载我们定义的 org.luanlouis.jvm.load.Main类;

  5.  加载完成时候JVM会执行Main类的main方法入口,执行Main类的main方法;

  6.  结束,java程序运行结束,JVM销毁。
  
  若要了解Launcher等每一步流程,可参考https://blog.csdn.net/luanlouis/article/details/50529868

JVM——类加载的更多相关文章

  1. JVM类加载过程学习总结

    JVM类加载过程学习总结 先不说JVM类加载的原理,先看实例: NormalTest类,包含了一个静态代码块,执行的任务就是打印一句话. /** * 在正常类加载条件下,看静态代码块是否会执行 * @ ...

  2. JVM类加载续

    上一篇理解了JVM类加载过程的第一个阶段,这篇来说说剩下的阶段:验证.准备.解析.初始化.需要注意的是,这些阶段(解析除外)只是按照这个顺序开始,但是执行的过程中可能存在交叉. 验证:就是要对加载的二 ...

  3. JVM类加载以及执行的实战

    前几篇文章主要是去理解JVM类加载的原理和应用,这一回讲一个可以自己动手的例子,希望能从头到尾的理解类加载以及执行的整个过程. 这个例子是从周志明的著作<深入理解Java虚拟机>第9章里抄 ...

  4. JVM类加载机制以及类缓存问题的处理

    一:JVM类加载机制 和 类缓存问题的处理 当一个java项目启动的时候,JVM会找到main方法,根据对象之间的调用来对class文件和所引用的jar包中的class文件进行加载(其步骤分为加载.验 ...

  5. JVM基础系列第7讲:JVM 类加载机制

    当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...

  6. 【深入Java虚拟机】一 JVM类加载过程

    首先Throws(抛出)几个自己学习过程中一直疑惑的问题: 1.什么是类加载?什么时候进行类加载? 2.什么是类初始化?什么时候进行类初始化? 3.什么时候会为变量分配内存? 4.什么时候会为变量赋默 ...

  7. JVM总结(四):JVM类加载机制

    这一节我们来总结一下JVM类加载机制.具体目录如下: 类加载的过程 类加载过程概括 说说引用 详解类加载全过程: 加载 验证 准备 解析 初始化 虚拟机把描述类的数据从Class文件加载到内存,并对数 ...

  8. JVM 类加载机制详解

    如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lan ...

  9. Java虚拟机(四):JVM类加载机制

    1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

  10. JVM类加载机制详解(二)类加载器与双亲委派模型

    在上一篇JVM类加载机制详解(一)JVM类加载过程中说到,类加载机制的第一个阶段加载做的工作有: 1.通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件).而获取的方式,可 ...

随机推荐

  1. oracle查询十分钟之前的数据

    select * from TABLE as of timestamp sysdate - 10/1440 t WHERE ColName='1111'; TABLE:表名 WHERE:查询子句 sy ...

  2. ml

    基础篇: 1. 读书<Introduction to Data Mining>,这本书很浅显易懂,没有复杂高深的公式,很合适入门的人.另外可以用这本书做参考<Data Mining ...

  3. Snort Inline IPS Mode

    Snort Inline IPS Mode https://forum.netgate.com/topic/143812/snort-package-4-0-inline-ips-mode-intro ...

  4. [书籍翻译] 《JavaScript并发编程》第七章 抽取并发逻辑

    本文是我翻译<JavaScript Concurrency>书籍的第七章 抽取并发逻辑,该书主要以Promises.Generator.Web workers等技术来讲解JavaScrip ...

  5. jar下载及Maven配置整理

    Spring的各版本jar包下载地址http://repo.spring.io/release/org/springframework/spring/ springframework下载地址https ...

  6. 【接口自动化】mock

    mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法. 1.在测试接口时使用mock #from unittest import mock d ...

  7. Spark学习笔记2——RDD(上)

    目录 Spark学习笔记2--RDD(上) RDD是什么? 例子 创建 RDD 并行化方式 读取外部数据集方式 RDD 操作 转化操作 行动操作 惰性求值 Spark学习笔记2--RDD(上) 笔记摘 ...

  8. DNS服务和BIND

    今天我们来介绍一下DNS服务,在大家的印象中DNS可能只是将域名解析为IP地址,可能其他的暂时还不太了解,希望本篇内容能帮助大家. 1.什么是DNS? DNS( Domain Name System) ...

  9. SVN版本控制—branches、trunk、tag篇

    新建资源仓库时,可选择默认创建三个文件夹.这三个文件夹分别是[trunk][branches][tags] [Trunk] 一般用于存放目前项目主线,也就是项目所有功能模块的集合体,一整个项目所有代码 ...

  10. Codeforces 348 D - Turtles

    D - Turtles 思路: LGV 定理 (Lindström–Gessel–Viennot lemma) 从{\(a_1\),\(a_2\),...,\(a_n\)} 到 {\(b_1\),\( ...