一个java文件从被加载到被卸载这个生命过程,总共要经历4个阶段:
加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载


类加载过程 - 图1

类加载过程
1. 加载 (类要做对应的加载 )
2. 连接
验证
准备
解析
3. 初始化 (加载器做的初始化)
4. 使用 (对象new出来,然后在业务代码里面使用.)
5. 卸载(是垃圾回收器去对类进行回收)

1.加载

加载阶段指的是将类的.class文件中的二进制数据读入到内存中,注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象(JVM规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在方法区中),用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载过程 - 图2


总结

加载阶段简单来说就是:
.class文件(二进制数据)——>读取到内存——>数据放进方法区——>堆中创建对应Class对象——>并提供访问方法区的接口

加载.calss文件的方式:
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。通过使用不同的类加载器,可以从不同来源加载类的二进制数据,二进制数据通常有如下几种来源:

(1)从本地系统中直接加载
(2)通过网络下载.class文件
(3)从zip,jar等归档文件中加载.class文件
(4)从专用数据库中提取.class文件
(5)将java源文件动态编译为.class文件

2.连接

验证

验证阶段主要是确保被加载的类的正确性.这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全.

主要是四个阶段:


1. 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以 0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
2. 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了 java.lang.Object之外。
3. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
4. 符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备


当完成字节码文件的校验之后,JVM 便会开始为类变量分配内存并初始化。准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

被final修饰的static变量(常量),会直接赋值;

解析


将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个可以在初始化之后再执行。

解析需要静态绑定的内容。 // 所有不会被重写的方法和域都会被静态绑定

以上2、3、4三个阶段又合称为链接阶段,链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中。

我们中间有静态解析和动态连接,这个就是方法的解析,比如你要把demo类的.king方法,这个常量池中间的一个方法代号解析成你直接的地址(0000013).
以后我如果碰到king方法我可以通过解析之后我就编程标号为0000013的地址,它去访问地址就可以了.
所以虚拟机会将很多里面的符号的引用替换成直接引用的过程,我们就称之为解析过程

3.初始化

到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。

初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。

初始化的5种情况
初始化阶段,虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

静态代码块儿,有多线程的问题,初始化的过程一定是线程安全的,多个线程调用同一个类的初始化,除了第一个访问到静态代码块儿的线程,其它线程就会阻塞,那么我始终就会有一个线程才会完成一个初始化操作,static代码块儿只会加载一次.

也就是说你自己写代码的时候,你可能不知道多线程的东西,但是你可以一些对线程安全要求很高的代码,写在static代码块儿里面.

案例:ZJJJavaBasic_2019/10/09 5:06:13_fq276

4.使用


当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。这个使用阶段也只是了解一下就可以了。

5.卸载

当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。这个卸载阶段也只是了解一下就可以了。