不了解JVM的类加载机制你也能够coding,可是当你了解以后,可让你在coding的时候避免不少坑,本文将以一道常见的面试题去剖析一下。本文参考深刻理解Java虚拟机(第2版)。java
1public class ClassLoadTest {
2 private static ClassLoadTest test = new ClassLoadTest();
3
4 static int x;
5 static int y = 0;
6
7 public ClassLoadTest() {
8 x++;
9 y++;
10 }
11
12 public static void main(String[] args) {
13 System.out.println(test.x);
14 System.out.println(test.y);
15 }
16}
复制代码
这里你们能够先猜想一下答案,可能结果会出乎你的意料~面试
先用一个图简单的描述一下类加载的这个过程
网络
这个过程至关于从本地或者网络端去读取一个字节流,而后将一些静态储存结构转换成方法区中运行时期的数据,最后生成一个表明这个类的Class对象,做为方法区访问这个类的入口。
例如:app
针对上述例子,这里是加载一个ClassLoadTest.class
对象。布局
要理解这个环节并非很难,一个东西要放到JVM上去运行,我们确定得对其进行一些过滤,不能啥都往上丢,这里的验证简单的举几个例子:spa
这个过程至关于给类变量分配内存并设置变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。线程
针对上述例子:3d
1test = null;
2x = 0;
3y = 0;
复制代码
注意:这里有个特殊状况,若是该字段被final
修饰,那么在准备阶段改字段就会被设置成我们自定义的值。public static final int value = 11
,在准备阶段就会直接赋值11,并非该变量的初始值。代理
将符号引用转换成直接引用的过程。这里有两个名词符号引用和直接引用。指针
- 符号引用:符号引用与虚拟机的布局无关,甚至引用的目标不必定加载到了内存中。符号能够是任何形式的字面量,只要使用时可以准确的定位到目标便可。
- 直接引用:直接引用能够直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用与虚拟机布局有关,若是有了直接引用,那么引用的目标一定已经在内存中存在。
而解析过程又会针对类、字段、方法进行解析,解析失败则会抛出相应的异常。例如在解析时发现没有访问权限会抛出java.lang.IllegalAccessException
异常,查询不到引用字段会抛出java.lang.NoSuchFieldException
异常,查询不到方法会抛出java.lang.NoSuchMethodException
异常等等。
在准备阶段,变量已经赋值过系统要求的默认值,在初始化阶段,则会根据程序制定的主观计划去初始化类变量和其余资源。这句话听起来有些绕口,根据上述例子,实际上就是:
1test = new ClassLoadTest();// x = 1;y =1
2y = 0;
复制代码
这个过程,因为x
我们本身并无去设定一个值,因此初始化阶段它不会发生任何改变,可是y
我们有设定一个值0,因此最后形成最终结果为x = 1;y = 0
。
ps:在同一个类加载器下,一个类只会初始化一次。多个线程同时初始化一个类,只有一个线程能正常初始化,其余线程都会进行阻塞等待,直到活动线程执行初始化方法完毕。
对于上面这个面试题,我们用流程图简单的描述一下: