jvm
java类的加载机制
1.什么是类的加载
类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放到运行时数据区的方法区中,然后在堆区中创建一个 java.lang.Class 对象,用来封装类在方法区的数据结构。类的加载最终产品是位于堆区的 Class 对象,该对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内数据结构的接口。
类加载器并不需要等到某个类被 首次主动使用 时在加载它,jvm规范运行类加载器预料到某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了 .class 文件缺失或者存在错误,类加载必须在程序首次主动使用该类时报告错误,如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
2.类的生命周期
类的加载过程包括了 加载、验证、准备、解析、初始化 五个阶段,在这五个阶段中, 加载、验证、准备、初始化 顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持java语言的动态绑定。另外这几个阶段是 按顺序开始,而不是按顺序完成,因为这些阶段通常都是互相交叉的混合进行的,通常在一个阶段的执行过程中调用或者激活另一个阶段。
加载
- 通过一个类的全限定类名来获取其定义的二进制字节流
- 将这个字节流所代表的静态存储结构转换为方法区中的运行时数据结构
- 在Java堆中生成一个代表这个类的 java.lang.class对象,作为对方法区中这些数据的访问入口
开发人员既可以使用系统提供的类加载器来完成加载,也可以定义自己的类加载器来完成加载。
验证
验证是连接阶段的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
正式为类变量分配内存并设置类变量初始值的阶段,而这些内存都将在方法区中分配
- 这个时候进行内存分配的仅仅包含类变量(static),而不包含实例变量,实例变量会在对象实例化时随着对象一块分配在java堆中
- 这里所设置的初始值通常情况下是数据类型默认的零值,而不是在java代码被显示赋予的值。
- 如果类字段的字段属性表中存在 ConstantValue属性, 即同时被final和static修饰,那么在准备阶段变量value就会被初始化。
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对 类或者接口、字段、类方法、接口方法、方法类型、方法句柄、和调用点限定符
等。
- 符号引用: 一组符号描述目标,可以是任何字面量
- 直接引用: 直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄
初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。
- 声明类变量是指定初始值
- 使用静态代码块为类变量指定初始值
JVM初始化步骤
- 如果这个类还没有被加载,则程序先加载并连接该类
- 如果该类的直接父类还没有被初始化,则首先初始化其直接父类
- 如果类中有初始化语句,则系统依次执行初始化语句。
导致类初始化的情况
- 创建类的实例,也就是new的方法
- 访问某个类或者接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(Class.forName(“com.qxy.test”))
- 初始化某个类的子类,则父类也会别初始化
- java虚拟机启动时被表明是启动类的类,直接使用java.exe命令来运行某个主类
结束生命的几种情况
- 执行System.exit()方法
- 程序正常执行
- 程序在执行过程中遇到了异常或者错误而终止
- 由于操作系统出现错误而导致java虚拟机进程终止
3.类加载器
几种类加载器
- 启动类加载器(Bootstrap Classloader) 负责加载放在 JDK\jre\lib或者被 -Xbootclasspath参数 指定的路径中,并且能被虚拟机识别的类库。 启动类加载器无法被java程序直接引用
- 扩展类加载器(Extension ClassLoader): 该加载器负责加载 JDK\jre\lib\ext 或者 java.ext.dirs 系统变量指定的所有类库,开发者可以直接使用该扩展类加载器
- 应用程序类加载器(Application classLoader): 负责加载用户类路径所指定的类,如果应用程序中没有自定义过自己的类加载器,一般情况下就是程序中默认的类加载器。
jvm类的加载机制
- 全盘负责: 当一个类加载器负责加载某个class时,他class所依赖的和引用的其他class也将由该类加载器负责载入,除非显示的使用另外一个类加载器来载入
- 父类委托: 先让父类加载器试图加载该类,只用在父类加载器无法加载该类时才会尝试从自己的类路径中加载该类
- 缓存机制: 缓存机制会保证所有加载过的Class都会被缓存,当程序需要使用某个类时,类加载器会先从缓冲区中寻找该类,只用缓冲区不存在,系统才会读取该类对应的二进制数据,并将其转换为Class对象,存入缓冲区。
4.类的加载
有三种方法
- 命令行启动应用时jvm初始化加载
- 通过**class.forName()**动态加载
- 通过 ClassLoader.LoadClass() 动态加载
class.forName() 和 ClassLoader.loadClass() 的区别
Class.forName()
:将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;ClassLoader.loadClass()
:只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。Class.forName(name,initialize,loader)
带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。
5.双亲委派机制
- 1、当
AppClassLoader
加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader
去完成。 - 2、当
ExtClassLoader
加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader
去完成。 - 3、如果
BootStrapClassLoader
加载失败(例如在$JAVA_HOME/jre/lib
里未查找到该class),会使用ExtClassLoader
来尝试加载; - 4、若
ExtClassLoader
也加载失败,则会使用AppClassLoader
来加载,如果AppClassLoader
也加载失败,则会报出异常ClassNotFoundException
。
作用
- 防止出现多份相同的字节码
- 保证java程序安全稳定的运行
6.自定义类加载器
重写findClass()方法
重写loadClass()会破坏双亲委派机制