什么是类的加载
类的加载指的是将类的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆内创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
类的加载器并不需要等到某个类被“首次主动加载”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载过程中遇到了.class 文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误),如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
加载 .class 文件的方式
- 从本地系统中直接加载
- 通过网络下载 .class 文件
- 从zip,jar 等归档文件中加载 .class 文件
- 从专有数据库中提取 .class 文件
- 将java源文件动态编译为 .class 文件
类的生命周期
类的生命周期为:加载、连接、初始化、使用、卸载 ,细分为:加载、验证、准备、解析、初始化、使用、卸载。
- 加载
查找并加载类的二进制数据,加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事:
1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在java堆中生成一个代表这个类java.lang.Class 对象,作为对方法区中这些数据的访问入口。
- 验证
验证阶段检查文件是否符合以下规范:
文件格式验证:验证字节流是否符合Class文件格式的规范。例如:是否以 0xCAFEBABE 开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求。例如:这个类是否有父类,除了java.Object 之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但是不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
-
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量是在对象实例化时随着对象一块分配在java堆中。
这里所设置的初始值通常情况下是数据类型默认的零值(如:0、0L、null、false等),而不是被在Java代码中被显示地赋予的值。
如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstantValue属性所指定的值。
- 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
- 初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。
类变量初始值设定方式:
1、声明类变量是指定初始值。
2、使用静态代码块为类变量指定初始值。
JVM初始化步骤
1、假如这个类还没有被加载和连接,则程序先加载并连接该类。
2、如果该类的直接父类还没有被初始化,则先初始化其直接父类。
3、假如类中有初始化语句,则系统执行一次执行这些初始化语句。
类初始化时机(直接引用)
1、通过new关键字实例化对象;
2、访问某个类或者接口的静态变量;
3、调用类的静态方法;
4、通过反射方式执行以上三种行为;
5、初始化某个类的子类,其父类也会被初始化;
6、java虚拟机启动时被标明为启动类,直接用java.exe命令来运行某个主类(也就是直接调用main方法)。
- 使用
类的使用包括主动引用和被动引用,主动引用在初始化的章节中已经说过了,下面我们主要来说一下被动引用:
1、引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
2、定义类数组,不会引起类的初始化。
3、引用类的常量,不会引起类的初始化。
- 卸载
在类使用完之后,如果满足下面的情况,类就会被卸载:
-
该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
-
加载该类的ClassLoader已经被回收。
-
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。
结束的时机:
1、执行了System.exit()方法;
2、程序正常执行结束;
3、程序在执行过程中遇到了一场或错误而异常终止;
4、由于操作系统出现错误而导致java虚拟机进程终止。
类加载器
启动类加载器(BootStrap ClassLoader)
Bootstrap ClassLoader 也称引导类加载器 使用C语言实现的。
负责加载存放在 JDK\jre\lib(JDK的安装目录,下同) 下,或被 -Xbootcalsspath 参数指定的路径中的,并且能被虚拟机识别的类库。
启动类加载器无法被Java程序直接引用。
扩展类加载器(Extension ClassLoader)
该加载器由 sun.misc.Launcher$ExtClassLoader实现。
负责加载存放在 JDK\jre\lib\ext目录中,或者有java.ext.dirs系统变量指定的路径中的所有类库(如javax. 开头的类)。
开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader)
该类加载器由 sun.misc.Launcher$AppClassLoader 来实现。
它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
如何自定义类加载器
继承ClassLoader 类,实现 findClass() 方法即可。
注意点:
1、类的名称,需要全限定名;
2、最好不要重写 loadClass() 方法,否则容易破坏双亲委派模型;
3、能被APPClassLoader 类加载的类不能放在类路径下,否则由于双亲委派机制的存在,导致APPClassLoader 直接加载。
JVM加载机制
- 全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式地使用另外一个类加载器来载入
- 父类委托
先让父类加载器试图加载该类,只有父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
- 缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。
应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的 java class 文件,因此如果编写了自己的 ClassLoader,便可以做到如下几点:
1、在执行非置信代码之前,自动验证数字签名。
2、动态地创建符合用户特定需要的定制化构建类。
3、从特定的场所取得java class ,例如数据库中和网络中。
类的加载
三种加载方式
1、命令行启动应用时候由JVM初始化加载
2、通过Class.forName()方法动态加载
3、通过ClassLoader。loadClass()方法动态加载
加载方式区别
1、Class.forName():将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中static块;
2、ClassLoader.loadClass():只干一件事,就是将 .class 文件加载到JVM,不会执行static中的内容,只有在newInstance才会去执行static块;
3、Class.forName(name,initialize,loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。
双亲委派模型
- 流程
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把请求委托给父加载器在其去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器,只有当父加载器在他的负责加载的范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
- 机制
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找该class),会使用ExtClassLoader来尝试加载。
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
- 意义
- 系统类防止内存中出现多份同样的字节码
- 保证java程序安全稳定运行