一,类的生命周期
-
加载
-
连接
-
验证
-
准备
-
解析
-
-
初始化
-
使用
-
卸载
-
加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定;
-
类加载几个阶段按顺序开始,而不是按顺序进行或完成,通常都会交叉进行;
1,加载
- 通过一个类的全限定名来获取二进制字节流(加载.class文件的方式:本地、网络、数据库等);
- 将字节流所代表的静态存储结构转化为 方法 区的运行时数据结构;
- 在 Java堆 中生成一个代表这个类的 Class对象( 类加载的 最终产品),Class对象封装了类在方法区内的数据结构,并且提供了访问方法区内的数据结构的接口。
- 类加载器:可用系统的或自定义的;
- JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,预加载时遇到错误,类加载器须在程序首次主动使用该类时才报告错误(没使用就不会报错)
2,连接
1,验证:确保被加载类的正确性
- 是否以0xCAFEBABE开头、 主次版本号是否在当前虚拟机的处理范围、 是否有父类、语义是否合法等;
2,准备:为类的静态变量分配内存,并将其初始化为默认值。
- 仅分配静态变量,实例变量会在实例化时随着对象一块分配在Java堆;
- 所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是显式地赋予的值;
- 如果字段同时被final和static修饰,那么会被初始化为指定的值;
3,解析:把类中的符号引用转换为直接引用,解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。JVM - 符号引用转化为直接引用_符号引用转为直接引用-CSDN博客
3,初始化
主要对类变量进行初始化,有两种方式:
① 声明类变量时指定初始值;
② 使用静态代码块为类变量指定初始值;
- 步骤:
- 假如这个类还没有被加载和连接,则程序先加载并连接该类;
- 初始化父类、执行初始化语句、构造函数等,参考: JVM 类初始化过程
- 时机: 只有 当对类的主动使用 的时候才会导致类的初始化,主动使用包括六种:
1.new对象;
2.访问某个类或接口的静态变量,或者对该静态变量赋值;
3.调用类的静态方法;
4.反射(如 Class.forName("com.shengsiyuan.Test"));
5.初始化某个类的子类,则其父类也会被初始化;
6.Java虚拟机启动时被标明为启动类的类(JavaTest),直接使用java.exe命令来运行某个主类;
二,类加载器
类加载器的层次关系:
- 父类加载器并不是通过继承关系来实现的,而是采用组合实现的。
1,三个类加载器
**启动类加载器:**BootstrapClassLoader
- 加载存放在 JDK\jre\lib**下,或被 -Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java. 开头的类均被 BootstrapClassLoader加载);
- 启动类加载器是无法被Java程序直接引用的。
扩展类加载器: ExtClassLoader
- 加载 JDK\jre\lib\ext**中,或者由 java*.ext.dirs系统变量指定的路径中的所有类库(如javax.* 开头的类),开发者可以直接使用扩展类加载器;
应用程序类加载器: AppClassLoader
- 负责加载用户类路径(ClassPath)所指定的类,一般情况下这个就是程序中默认的类加载器;
2,JVM类加载机制
全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
**父类委托:**双亲委派模型
先让父类加载器试图加载该类,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
步骤:
- 1、当 AppClassLoader 加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器 ExtClassLoader 去完成。
- 2、当 ExtClassLoader 加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
- 3、如果 BootStrapClassLoader 加载失败(例如在 $JAVA_HOME / jre / lib 里未查找到该class),会使用 ExtClassLoader 来尝试加载;
- 4、若ExtClassLoader也加载失败,则会使用 AppClassLoader 来加载,如果 AppClassLoader 也加载失败,则会报出异常 ClassNotFoundException 。
意义:
- 系统类防止内存中出现多份同样的字节码
- 保证Java程序安全稳定运行
缓存机制
所有加载过的Class都会被缓存。当程序使用某个Class时,类加载器先从缓存区寻找该Class,缓存不存在时才加载类并存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。
三,类的加载方式
类加载有三种方式:
- 1、命令行启动应用时候由JVM初始化加载
- 2、通过Class.forName()方法动态加载
- 3、通过ClassLoader.loadClass()方法动态加载
四,自定义类加载器
有时候我们需要自定义类加载器。比如应用是通过网络来传输 Java类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。
自定义类加载器一般都是继承自 ClassLoader类,从上面对 loadClass方法来分析来看,我们只需要重写 findClass 方法即可。
自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:
- 这里传递的文件名需要是类的全限定性名称;
- 最好不要重写loadClass方法,因为这样容易破坏双亲委托模式;
- 这类Test 类本身可以被 AppClassLoader 类加载,因此不能把 com / paddx / test / classloading / Test . class 放在类路径下。否则会直接导致该类由 AppClassLoader 加载;