JVM类加载过程
类加载过程
Class文件加载到虚拟机中
流程就是加载 - 连接 - 初始化,其中连接部分又分为 验证 - 准备 - 解析 到这over
(也有说法是五个阶段,加载,验证,准备,解析,初始化 连着进行,其实是一样的,【其中解析阶段的顺序不一定,有可能初始化之后才开始】)
加载阶段
-
通过全类名获取定义此类的二进制字节流
-
将字节流所代表的静态存储结构转换为方法区的运行时数据结构
-
在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口
简单的讲就是 获取字节流,方法区生成数据结构,内存生成Class对象作入口
如果是非数组类的加载,我们可以通过自定义加载器来控制字节流的获取(通过重写ClassLoader的loadClss()方法)
数组类不通过类加载器创建,而是由Java虚拟机直接创建
(什么是数组类,翻的)
验证阶段
我的理解就是验证阶段主要验证类的文件啊 字节码啊 各类符号语法啥的 是否符合Java规范,并且不会危害虚拟机自身的安全。
准备阶段
分配类中的变量(static修饰的静态变量) , 分配内存并设置初始值
1.初始值都是0 0L false这种, 等到初始化的时候才会赋给真实值
2.如果是final修饰的常量 直接赋值真是值
在准备阶段a的值是0,B的值是123。
解析阶段
解析阶段Java虚拟机将常量池内的符号引用替换为直接引用
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
- 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,引用的目标并不一定是已经加载到虚拟机内存当中的资源;
- 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄,如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在;
在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,
只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
简单的讲 解析阶段就是得到类或者字段、方法在内存中的指针或者偏移量。
初始化阶段
真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器 <clinit> ()
方法的过程。
在该方法中,才为变量赋真实的值
<clinit>()方法是由编译器自动收集类中的所有类变量(static变量)的赋值动作和静态语句块(static{}块)中的语句合并产生的,收集顺序是源文件中的代码顺序;
() 方法不是必须的,如果我们的源文件中没有静态语句块和静态属性的赋值,那么久不会有() 方法。
子类() 方法执行之前需要保证先执行父类的() 方法,所以Object类的() 方法是第一个执行的
对于<clinit>()
方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 <clinit>()
方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。
有且只有5中情况下,必须对类进行初始化:
-
当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
-
使用
java.lang.reflect
包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。 -
初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
-
当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
-
当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个句柄没有初始化,则需要先触发器初始化。
so 当子类引用父类的static变量时,父类并不会初始化
Tomcat类加载机制
tomcat热部署,热加载了解么,怎么做到的?
Tomcat本身就是一个JVM进程,当Tomcat启动后,各Web应用打成war包部署过来如何加载到JVM中
Tomcat根据自定义类加载器,打破了双亲委派原则,
加载类首先会经过WebApp类加载器,每个Web应用都会对应一个WebApp类加载器,该加载器能加载war包下所有.class文件