这一节我们来总结一下JVM类加载机制。具体目录如下:

  虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是Java虚拟机的类加载机制。

  在Java中,类型的加载、连接和初始化过程都在程序运行期间完成的,这种策略虽然会使类加载时增加一些性能开销,但是提供了高度的灵活性,Java里天生可以动态扩展的语言特就是依赖于运行期动态加载和动态连接的特点实现的。 
  Class文件指的是一串二进制的字节流。实际上,每个Class文件都有可能代表着Java语言中的一个类或者接口。

类加载的过程

类加载过程概括

  在这七个过程中,加载、验证、准备、初始化、卸载这5个阶段的顺序是一定的,类的加载过程必须按照这种顺序按部就班地开始,而解析过程则不一定:它在某个情况下可以在初始化阶段之后再开始,这是为了支持Java语言语言的运行时绑定(也叫动态绑定和晚期绑定)。 
  这里强调的是:类加载阶段都是互相交叉地混合式进行的,通常是在一个阶段执行的过程中调用、激活另一阶段。

说说引用

  对类的初始化操作可分为主动引用和被动引用 
主动引用:在以下5种情况下会进行类的主动引用的初始化操作:

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行初始化,则需要先触发其初始化。生成这4条指令最常见的代码情景是:使用new关键字实例化对象、读取过设置一个类的静态字段(被final修饰、已在编译期把结果放进常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  • 使用java.lang.reflect包对类进行反射调用时,如果类没有进行过初始化,则应需要先触发其初始化。
  • 当初始化一个类时,如果发现其父类还没有进行过初始化,会触发其父类实例化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法类),虚拟机会先实例化那个类。
  • 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例的最后解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则要先触发其初始化。 
    被动引用:所有引用类的方式都不会触发初始化。 
    对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。 
    通过数组定义引用类,不会触发此类初始化:当初始化对象数组时,并不会实际触发对象的初始化操作。但是会触发一个是由虚拟机自动生成的、直接继承于java.lang.Object的子类,创建动作由字节码指令newarray触发。值得注意的是:该类代表了实际的对象数组,数组中应有的方法和属性都实现在这个类里。Java语言对数组的访问比C/C++相对安全是因为这个类分装了数组元素的访问方法。 
    常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义类的初始化。

值得注意的是: 
  接口也有自己的初始化过程:编译器会为接口生成“()”类构造器,用于初始化接口中所定义的成员变量。 
  接口和类初始化的区别:当一个类在初始化时,其父类都基本上初始化过了,然而接口在初始化的时候,只有真正用到父接口的时候(如引用接口中定义的常量)才会进行初始化。

详解类加载全过程:

加载

在加载阶段,虚拟机需要完成以下3件事情:

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

  非数组类的加载是可控性最强的。用户除了使用系统提供的引导类加载类来完成,也可以由用户自定义的类加载器去加载(重写一个类加载器的loadClass())。 
  注意:数组类本身不通过类加载器创建,它是由JVM直接创建的。但数组类和类加载器仍有很紧密的关系,因为数组类的元素类型最终是靠类加载器去创建。 
  加载完成后,虚拟机外部的二进制字节流就按照虚拟机所需格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义。然后在内存中实例化一个java.lang.Class类的对象(可以在Java堆中,也可以在方法区中),该对象将作为程序去访问方法区中的这些类型数据的外部接口。 
  加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未结束,连接阶段就可能开始了。但是夹在加载阶段进行的动作,仍然属于连接阶段的内容。

验证

  验证是连接的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危及虚拟机本身的安全。 
验证阶段的四个步骤:文件格式检验、元数据检验、字节码检验、符号引用检验。

  • 文件格式检验: 
    检验字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。检验可能包含下列几种:是否以魔数开头、主次版本号是否在虚拟机的处理范围之内,常量池中的常量是否不被支持、文件是否被删除或附加什么信息等等。 
    只有通过文件格式检验的二进制字节流才能进入内存的方法区进行存储,所以后面的3个检验阶段都是基于方法区的存储结构进行的,不会在操作字节流。
  • 元数据检验: 
    对字节码描述的信息进行语义分析,以保证其描述的内容符合Java语言规范的要求。 
    验证点包括:是否有父类(除了object)、父类是否继承了不可被继承的类(被final修饰的类)、如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法、类中的方法和字段是否与父类产生矛盾(覆盖了父类的final字段、出现不合规矩的方法重载等)。 
    元数据检验主要是对类的元数据信息进行语义校验,保证不符合Java语言规范的元数据信息不存在。
  • 字节码检验: 
    通过数据流和控制流分析,确定程序语义是合法、符合逻辑的。第二阶段是对元数据信息中的数据类型做了检验,这一阶段是对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事情。 
    检验点包括:保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作、保证指令跳转不会跳转到方法体之外的地方、保证方法体内的类型转换都是有效的。 
    事实上,即便是经过字节码检验后的方法体也不一定是安全的。
  • 符号引用检验 
    最后一个检验发生在虚拟机将符号引用转化为直接引用时,这个转化动作将在连接的第三阶段–解析阶段中发生的。符号引用检验可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。 
    校验点:符号引用中通过字符串描述的全限定名是否能找到对应的类、在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段、符号引用中的类、字段、方法的访问权限是否能让当前类访问到等。 
    符号引用检验的目的是确保解析动作的正常执行,如果无法通过符号引用检验,将会抛出java.lang.IncompatibleClassChangeError异常的子类,如IllegalAccessError、NoSuchfiledError、NoSuchMethodError等。

准备

  准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这些变量所使用的内存将在方法区中进行分配。此时进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。另外,在这里分配的静态类变量是将其值定义为0等默认值,而不是我们定义的。因为这时尚未执行任何Java方法,我们定义的赋值的putStatic指令是程序被编译后,存放在类构造器()方法中,所以正确的赋值将在初始化阶段执行。 
  如果类变量被final修饰,那么在这种情况下,在编译时Javac将会为该变量生成ConstantValue属性,在准备阶段虚拟机会根据该属性设置类变量的正确值。

解析

  解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。 
a、符号引用:以一组符号来描述所引用的目标,符号可以是任何形式字面量,只要使用时无歧义地定位到目标就行。 
b、 直接引用:直接引用是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。引用的目标已经在内存中存在。 
  虚拟机实现可以根据需要来判断到底在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用时才去解析它。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

初始化

类加载的最后一步,真正执行类中定义的Java程序代码(字节码)。 
初始化阶段是执行类构造器()方法的过程,根据程序员通过程序制定的主观的计划去初始化类变量和其他资源。

JVM总结(四):JVM类加载机制的更多相关文章

  1. JVM学习笔记(四):类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 一.类加载的时机1. 类从被加载到虚拟机内存 ...

  2. 【JVM.6】虚拟机类加载机制

    一.概述 虚拟机类加载机制:虚拟机把描述类的数据从Class文件中加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型. 与那些在编译时需要进行连接工作的语言不同 ...

  3. JVM内存模型与类加载机制

    一. java虚拟机的内存模型如图: 补习一下jvm内存模型中的各个组成部分 堆: 我们new出来的对象全部放在堆中,他是jvm所能够动态分配的最大的一块空间 优点: 内存动态分配,生命周期不必事先告 ...

  4. jvm字节码和类加载机制

    Class类文件的结构 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(类和接口也可以用反射的方式通过类加载器直接生成) Class文件时一组以 ...

  5. JVM(三)-java虚拟机类加载机制

    概述: 上一篇文章,介绍了java虚拟机的运行时区域,Java虚拟机根据不同的分工,把内存划分为各个不同的区域.在java程序中,最小的运行单元一般都是创建一个对象,然后调用对象的某个 方法.通过上一 ...

  6. 玩命学JVM(二)—类加载机制

    前言 Java程序运行图: 上一篇玩命学JVM(一)-认识JVM和字节码文件我们简单认识了 JVM 和字节码文件.那JVM是如何使用字节码文件的呢?从上图清晰地可以看到,JVM 通过类加载器完成了这一 ...

  7. 深入理解JVM虚拟机-7虚拟机类加载机制

    虚拟机把描述类的数据从Class文件夹加载到内存,并对数据进行小燕.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制. 下面所说的Class文件不是具体的某个文件 ...

  8. JVM系列3:类加载机制

    了解类加载机制也是深入了解Java的重要一环,它包括加载过程.类加载器.加载机制等内容. 以下是我总结的思维导图. 首先讲讲类加载的时机,以下是会触发类加载的时机: 1.new.get/put/inv ...

  9. jvm系列(四):jvm知识点总结

    原文链接:http://www.cnblogs.com/ityouknow/p/6482464.html jvm 总体梳理 jvm体系总体分四大块: 类的加载机制 jvm内存结构 GC算法 垃圾回收 ...

  10. JVM 专题四:类加载子系统(二)双亲委派机制

    2. 双亲委派机制 2.1 双亲委派机制工作原理 2.1.1 原理 Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存,生成class对象 ...

随机推荐

  1. Using Android Phone to recover SD card formatted with DD command under linux

    Using Android Phone to recover SD card formatted with DD command under linux 1. Formatted a sd card ...

  2. Jfrog Artifactory 创建docker 镜像仓库以及 push 镜像到 该仓库.

    1. 安装aitifactory 以及 启动 使用30天有效期激活 不在阐述. 2. 登录artifactory username:admin password:password 3. 创建 仓库 在 ...

  3. 无法定位程序输入点 zend_empty_string php7.dll

    phpstudy 在安装php_redis.dll php_redis.pdb 时,需要用到php_igbinary.dll扩展.但我下载的版本不对.下载的是7.2版本的. 所以报以上错误.可选择版本 ...

  4. Spring AOP切点表达式用法总结

    1. 简介        面向对象编程,也称为OOP(即Object Oriented Programming)最大的优点在于能够将业务模块进行封装,从而达到功能复用的目的.通过面向对象编程,不同的模 ...

  5. python之pygal:掷两个不同的骰子并统计大小出现次数

    代码示例: # 掷两个不同的骰子并统计大小出现次数 import pygal from die_class import Die die = Die(6) # 实例化一个六面的骰子对象 die_10 ...

  6. 51nod 1092(lcs)回文字符串

    题目:给你一个字符串,问添加最少的字符数目,使之成为回文串 解题思路:将字符串倒置,求出字符串和倒置串的最长公共子序列,字符串的长度减去lcs的长度就是了.. 代码:#include<iostr ...

  7. windows编程按小时生成日志文件

    这是一个简单的日志记录方法,为了避免单个日志文件过大,所以每个小时生成一个新的日志文件 注意:g_pLogPath 可以带路径,但是必须手动创建好路径,保证目录存在.而且要详细到log文件名,不能带后 ...

  8. BZOJ4538 HNOI2016网络(树链剖分+线段树+堆/整体二分+树上差分)

    某两个点间的请求只对不在这条路径上的询问有影响.那么容易想到每次修改除该路径上的所有点的答案.对每个点建个两个堆,其中一个用来删除,线段树维护即可.由于一条路径在树剖后的dfs序中是log个区间,所以 ...

  9. JAVA内部类小结

    内部类的概念:定义在其他类里面的类叫做内部类,包含内部类的类叫做外部类: 内部类的作用:内部类主要是用来描述一个事物存在于另一个事物里面,依赖于外部事物存在的: 内部类的格式: 内部类的分类: 成员内 ...

  10. Spring JDBC 数据访问

    Spring JDBC是Spring所提供的持久层技术,它的主要目标是降低使用JDBC API的门槛,以一种更直接,更简介,更简单的方式使用JDBC API, 在Spring JDBC里,仅需做那些与 ...