JVM-类加载机制

类的加载:

  将类的.class文件中的二进制数据读入内存,将其放在方法区中,然后在堆中建立java.lang.class对象,用来封装类在方法区内的数据结构,并向开发者提供访问方法区内的数据结构的入口。类加载器不需要等待某个类被首次主动使用时再加载。JVM规范允许类加载器在预料某个类将要被使用时就预先加载,在该过程中遇到.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError),如果该类一直没被程序主动使用,那么类加载器不会报告错误。


 加载.class文件的方式:

  1、从本地系统中直接加载

  2、通过网络下载.class文件

  3、从zip、jar等归档文件中加载.class文件

  4、从专有数据库中提取.class文件

  5、从Java源文件动态编译为.class文件


类的生命周期:

   

  类的加载过程包含加载、验证、准备、解析、初始化。其中加载、验证、准备、和初始化发生顺序是确定的,但解析阶段不一定,它在某种情况下可以在初始化阶段后开始。这是为了支持Java语言的运行时绑定。这几个阶段按顺序开始,但不是按顺序进行或完成。


加载:

  主要完成查找并加载类的二进制数据。此阶段,JVM完成了三个事情:

  1、通过类的全限定名来获取其定义的二进制字节流

  2、将这个字节流待表的静态存储结构转化为方法区的运行时数据结构

  3、在Java堆中生成一个待表这个类的Java.lang.class对象,作为对方法区中这些数据的访问入口

  加载阶段是可控性最强的阶段,开发者可使用系统提供的类加载器来完成加载,也可自定义类加载器完成加载


连接:验证+准备+解析

验证:

  目的是确保被加载的类的正确性,保证Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。该阶段四个校验动作:

  1、文件格式验证:验证字节流是否符合Class文件格式规范;如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机处理范围、常量池中的常量是否有不被支持的类型。

  2、元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。

  3、字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

  4、符号引用验证:确保解析动作能正确执行

该阶段非常重要但非必须,如果所引用的类经过反复验证,那么可以通过-Xverifynone参数来关闭大部分的类验证措施,锁定虚拟机类加载时间


准备:

  为类的静态变量分配内存,并将其初始化为默认值,在方法区中分配。该阶段需要注意:

  1、此时分配内存的仅包括类变量,不包括实例变量。实例变量在对象实例化时随着对象一块分配在Java堆中

  2、此时设置的除初始量通常是数据类型的默认值(如0、0L、null、false等),而不是在代码中被显示赋予的值

Demo:

  public static int value= 6;当变量value在准备阶段后初始值为0,而不是6。赋值为6的动作将在初始化阶段执行。

  3、类变量的字段属性中存在ConstantValue属性,即同时被final和static修饰,那么准备阶段变量就会被初始化为ConstValue属性所指定的值。

Demo:

  public static final int value = 6;准备阶段value就被赋值为6

  注意:

    1、对应基本类型,对于类变量和全局变量,如果不显示的赋值而直接使用,系统会赋予默认值。局部变量不显示赋值,则编译不通过。

    2、同时被static和final修饰的常量,必须声明时显示赋值,否则编译不通过。

    3、引用数据类型,没有显示赋值时系统会赋予默认值null。

    4、数组初始化时没对元素赋值,则其中元素将根据对应数据类型被赋予默认值


解析:

  该阶段虚拟机将常量池内的符号引用替换为直接引用的过程。主要针对类、接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符。

  符号引用是一组符号来描述目标,可以是任何字面量。

  直接引用是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄


初始化:

  为类的静态变量赋予正确的初始值。主要方式有两种:

  1、声明类变量时指定初始值

  2、使用静态代码块为类变量指定初始值

  JVM初始步骤:

    1、当该类没有被加载和连接,则程序先加载并连接该类

    2、当该类的直接父类没有被初始化,则先初始化其直接父类

    3、当类中有初始化语句,则系统依次执行这些初始化语句

  类初始化时机:只有当对类的主动使用的时候才会导致类的初始化。类的主动使用包括:

  1、创建类的实例,new

  2、方法某个类或接口的静态变量,或对该静态变量赋值

  3、调用类的静态方法

  4、反射(如Class.forName(“com.shengsiyuan.Test”)

  5、初始化某个类的子类,则其父类也会被初始化

  6、Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类


生命周期的结束:

  1、执行了System.exit()方法

  2、程序正常执行结束

  3、程序在执行过程中遇到异常或错误而异常终止

  4、由于操作系统出现错误而导致Java虚拟机进程终止


类加载器:

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

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

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

  自定义类加载器(User ClassLoader):开发者编写的类加载器,可以做到如下

    1、在执行非置信代码之前,自动验证数字签名。

    2、动态地创建符合用户特定需要的定制化构建类。

    3、从特定的场所取得java class,例如数据库中和网络中。

  JVM类加载机制:

    1、全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

    2、父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

    3、缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效


 类的加载:

  类的加载有三种方式:

    1、命令行启动应用时候由JVM初始化加载

    2、通过Class.forName()方法动态加载

    3、通过ClassLoader.loadClass()方法动态加载

  Class.forName()和ClassLoader.loadClass()的区别:

    1、Class.forName()将类的.class文件加载到JVM中,还会对类进行解释,执行类中的静态代码块。

    2、ClassLoader.loadClass()只将类的.class文件加载到JVM中,不会执行类中的静态代码块。只有当newInstance()才会去执行。

    3、ClassLoader.loadClass(name,initialize,loader)带参函数也可控制是否加载静态块,并且只有调用了newInstance()方法才会调用构造函数,创建类的对象


 双亲委派模式:

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

  双亲委派机制:

    1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

    2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader```去完成。

    3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

    4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

  意义:

    1、系统类防止内存中出现多份同样的字节码

    2、保证Java程序安全稳定运行


参考资料:

  《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》

  《纯洁的微信-JVM系列文章》:http://www.ityouknow.com/jvm.html

  由衷的感谢提供学习资料的前辈们!!!

 

原文地址:https://www.cnblogs.com/zhangbLearn/p/10272373.html