我们都知道java 文件一次编译,到处运行,依赖于不同系统版本的jdk,最终达到与 wind/linux 等系统进行底层交互,从而保证程序能够在各个系统正常运行;
本文以以下3个方面研究jvm的类加载:
1 jdk 架构概要;
2 类如何被加载到 jvm 中;
3 类的加载过程;
1 jdk 架构概要:
Jdk 中包含一些工具类库,运行环境JRE(java核心类库);其中JRE 又包含了jvm(java 虚拟机),而我们自己些的代码最终都要放入到jvm 中进行运行;
2 类如何被加载到 jvm 中:
根据java万事万物皆对象的原则,Jvm 要想进行类加载,可以使用一个专门的类来负责进行,这个类就是类加载器(ClassLoader):
Jvm 中将类加载器进行了几个划分,来更加明确每个类加载器的责任:
如果我们没有自定义类加载器,那么一般情况下我们自己写的类都是交由AppClassLoader 进行的加载;
Jvm 在进行类加载过程中有几个机制:
(1)全盘负责:
用到的类由当前类加载器进行所有类的加载;只是调用了loadClass 没有进行真实的类加载;
(2)父类委托:
先从父类加载类;父类加载不到最终由自己加载(双亲委派机制:避免核心类库不被覆盖);
(3)缓存机制:
加载过的class 会被放入到内存中进行缓存,可以被后续直接使用;
3 类的加载过程:
Jvm 在使用到一个类的时候会先从内存中获取,如果获取不到则需要由类加载器,去依照类加载的3个机制去加载这个类;类加载时,如果有父类,会先加载父类;
其中类的加载可以详细的划分为以下几个步骤:
3.1 类装载:
第一步的装载,就是依照类加载的3个机制去加载这个类;
(3.1.1)从哪儿加载类?:
Jvm 会从以下几个部分去尝试加载类:
(3.1.2)如何找到这些文件:
通过权限命名,通过要用到类的全路径名找到这个文件,并将其转成文件流进行加载;
此时会将改类的一个class 访问对象放入到jvm 的运行时数据区的堆中;
3.2 链接:
(3.2.1)验证:验证文件格式是否正确,只有符合要求的文件才可以被保存到方法区之内:
如:验证文件格式
元数据验证:校验java语法
字节码验证:校验是否对jvm有危害
符号引用验证:
(3.2.2)准备:
(3.2.2.1)静态变量处理:
为类中的静态变量(static 修饰),分配内存空间并设置默认的初始值(分配到方法区);
注意:不会为不加static 的变量(即:类的实列变量)去进行分配;
(3.2.2.2)final static 修饰的常量 准备阶段就给到值:
反编译后:
ConstantValue:对于final static 修饰的常量 类型是基本类型/字符串时会在准备阶段赋值;
总结:
a: final修饰的实例属性,在实例创建的时候才会赋值。
b: static修饰的类属性,在类加载的准备阶段赋初值(基本类型进行初始赋值;引用类型赋null),初始化阶段赋值。
c: static+final修饰的String类型或者基本类型常量,JVM规范是在初始化阶段赋值,但是HotSpot VM直接在准备阶段就赋值了。
d: static+final修饰的其他引用类型常量,赋值步骤和第二点(b)的流程是一样的;
e: 对于其他的类成员变量则在类的对象构造方法中赋值;
(3.2.3)解析:
符号引用转为直接引用,真正指向一个物理地址
如果对同一个符号引用,多次使用,则jvm进行第一次解析之后缓存解析的结果;
(invokedynamic 指令 动态解析 真正使用的时候才知道使用的哪个)
(3.3)初始化:
(3.3.1)对被static 修饰的并在准备阶段赋予初始的类变量进行真正的赋值;
(3.3.2)执行staitic 修饰的静态代码块;
注意:
a 类变量初始值设定:是按照顺序赋值,所以一般需要先定义然后在在代码块赋值;
b 并不是在用到某个类时就会对该类进行初始化,初始化时机(主动使用的时候才会初始化):
下面面6中会进行初始化:
以下3种时机时不会对该类进行初始化的:
(3.4)使用:
经过前面3步我们就可以在程序种使用该类;
(3.5)卸载:
当类使用完毕后对类进行卸载,卸载的时机: