这个系列将主要围绕JVM中的类加载过程进行分析,以便于我们更好的理解JVM的运行机制,解决掉以下问题:
有效防止内存泄漏(
Memory leak
)。
优化线程锁的使用
(Thread Lock)
。
科学进行垃圾回收
(Garbage collection)
。
提高系统吞吐量
(throughput)
。
降低延迟
(Delay)
,提高其性能
(performance)
从JVM角度去分析我们的代码出现的问题。下面开始学习JVM中类的加载过程。
类加载的一个基本步骤如下:
1)
通过一个类的全限定名(类全名
-
包名
.
类名)来获取其定义的二进制字节流
(byte[ ])
。
2)
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)
在
Java
堆中生成一个代表这个类的
java.lang.Class
对象,作为对方法区中这些数
据的访问入口,如下图所示:
我们看到加载过程中大致可分为加载、验证、准备、解析、初始化这几个阶段,但这几个
阶段的执行顺序又是怎样的呢?
JVM
规范中是这样说的:
1)
加载、验证、准备和初始化发生的顺序是确定的,而解析阶段则不一定
.
2)
加载、验证、准备和初始化这四个阶段按顺序开始不一定按顺序完成。
另外,一个已经加载的类被卸载的几率很小,至少被卸载的时间是不确定的,假如需要
卸载的话可尝试
System.exit(0);
1.规范验证(Verification)
这一阶段的目的是为了确保Class
文件的字节流中包含的信息符合当前虚拟机的要求,
并且不会危害虚拟机自身的安全。
验证阶段大致会完成
4
个阶段的检验动作:
1) 文件格式的验证(魔数,版本号,常量池,访问标志,当前类索引,
…
)。
2) 元数据验证(
int a=10
,其中
int a
就为元数据)。
3) 字节码合法性验证(同一个
.java,
生成的字节码结构是固定,例如都是
16
进制)。
4) 符号引用验证(
Class
文件中以
CONSTANT_Class_info
、CONSTANT_Fieldref_info 等常量形式出现)
说明:验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用
的类经过反复验证,那么可以考虑采用
-Xverify:none
参数来关闭大部分的类验证措施,以
缩短虚拟机类加载的时间。
2.准备(Preparation)
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区
中分配。
1) 类变量(
static
)内存分配。
2) 按类型进行初始默认值分配(如
0
、
0L
、
null
、
false
等)对应的变量类型就是(int,long,对象,boolean)。
例如:假设一个类变量的定义为:public static int value = 3
;那么变量
value
在准备
阶段过后的初始值为
0
,而不是
3
,把
value
赋值为
3
的动作将在初始化阶段才会执行。
3) 如果类字段的字段属性表中存在
ConstantValue
属性,也就是常量,即同时被
final
和
static
修饰,那么在准备阶段变量 value
就会被初始化为
ConstValue
属性所指定的值。
例如:假设上面的类变量
value
被定义为:
public static final int value = 3
;编译时
Javac
将会为
value
生成
ConstantValue
属性,在准备阶段虚拟机就会根据
ConstantValue
的设置将
value
赋值为
3
3.解析(Resolution)
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,
其中:
1) 符号引用:就是一组符号(例如
CONSTANT_Fieldref_info
)来描述目标,可以是
任何字面量。
2) 直接引用:就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
说明:相同的符号引用不同
JVM
机器上对应的直接引用可能不同,直接引用一般对应
已加载到内存中的一个具体对象
以上过程都执行完成后才会进行类的初始化
此阶段为类加载的最后一个阶段,这个阶段的 JVM
负责对类进行初始化,主要对类变
量进行初始化。
在
Java
中,对类变量进行初始值的设定有两种方式:
1)
声明类变量时指定初始值。
2)
使用静态代码块为类变量指定初始值。