什么是类的加载
将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
类的生命周期
类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束。过程共有七个阶段,分别是加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸载(Unloading)七个阶段。其中验证、准备和解析三个部分统称为连接(Linking),
图解:
1.加载
查找并加载类的二进制数据,步骤如下:
通过一个类的全限定名来获取其定义的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
2.连接
将Java类的二进制代码合并到JVM的运行状态中的过程
(1)验证:确保加载的类信息符合JVM规范,没有安全方面的问题
文件格式的验证:是否以0xCAFEBABE开头、版本号是否合理
元数据验证:是否有父类、继承了final类、非抽象类实现了所有的抽象方法
字节码验证(复杂):运行检查、栈数据类型和操作码参数吻合、跳转指令到合理的位置
符号引用验证:确保解析动作能正确执行。
(2)准备:为类变量(static变量)分配内存并保存设置类变量初始值的阶段,这些值都在方法区中进行分配。(int a = 3;准备阶段a=0,在初始化时才赋值3)对static final类型的数据,在准备阶段就会被赋值。
(3)解析:将虚拟机常量池内的符号引用替换为直接引用的过程
3.初始化
(1)初始化阶段是执行类构造器()方法的过程,该方法由编译器自动收集类中的所有类变量的赋值操作和静态语句块中的语句合并产生的。
(2)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需先发出父类的初始化
(3)虚拟机会保证一个类的< clinit>()方法在多线程环境中被正确加锁和同步。
4.类初始化时机
只有当对类的主动使用的时候才会导致类的初始化
(1) 类的主动引用(一定会发生类的初始化)
① New 一个类的对象
② 调用类的静态成员(除了final常量)和静态方法
③ 使用java.lang.reflect包的方法对类进行反射调用
④ 当虚拟机启动时,java Hello,则一定会初始化Hello类,即先启动main方法所在的类
⑤ 当初始化一个类,若父类没有初始化则先会初始化父类
(2) 类的被动引用
① 当访问一个静态域时,只有真正声明这个域的类才会被初始化(通过子类引用父类的静态变量,不会导致子类的初始化
② 通过数据定义类的引用(定义对象数组),不会触发此类的初始化,
③ 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
④通过类名获取Class对象,不会触发类的初始化,
⑤通过ClassLoader默认的loadClass方法,也不会触发初始化动作
5.结束生命周期
Java虚拟机将结束生命周期有以下几种情况
执行了System.exit()方法
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止
双亲委派模型
1.JVM预定义的三种类加载器
如图
注:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的
(1)启动(引导)类加载器(bootstrap class loader)
加载Java的核心库,是用原生代码(c/c++)实现的,加载扩展、应用程序类加载器,并指定它们的父类加载器 rt.jar 参数:-Xbootclasspath
(2)扩展类载器(Extension ClassLoader):
该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器
(3) 应用程序类加载器(Application ClassLoader)
该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
(4) 自定义类加载器:通过继承java.long.Classloader类,来实现自己的类加载器。
2.JVM类加载机制
(1)全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
(2)父类委托
先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
(3)缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
3.类加载器的代理模式
(1) 定义:交给其他代理器来加载指定的类
(2) 双亲委托机制:
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,知道最高层,如果父类加载器可以完成类加载任务,就成功返回,只有父类加载器无法完成此加载任务时,自己才去加载。
为了保证Java核心库的类型安全(保证不会出现用户自己定义java.long.Object类的情况
也是安全的最基本屏障
(3) 双亲委托机制是代理模式的一种,并不是所有的类加载器都采用双亲委托机
制,
(4) Tomcat服务器加载器也使用代理模式,所不同的是它是首先去尝试加载某个类,如果找不到再代理给父类加载器,与一般类加载器的顺序相反
4.线程上下文加载器
基本思想:在顶层ClassLoader中,传入底层ClassLoader的实例。
通过Thread.setContextClassLoader()反复设置
一个角色、解决顶层ClassLoader无法访问底层ClassLoader类的问题、
5.OSGI模型
OSGI(Open Service Gateway Initative):Java的动态模块系统
核心:多个类加载器,谁的组件谁来加载
6.热替换
当一个class被替换后,系统无需重启,替换的类立即生效。
本人才疏学浅,若有错,请指出
谢谢