深入理解java虚拟机(四)
(一)概述:
注:解析阶段在某些情况下可以在初始化之后再开始,这是为了支持java的动态绑定。
5种情况对必须立即对类初始化:
1.遇到new,getstatic,putstatic或invokestatic这四条字节码指令。
2.使用java.lang.reflect包的方法对类进行反射调用的时候
3.当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
4.包含main方法的那个类会先初始化
5.如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putstatic,REF_invokestatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化
(二)具体过程:
1.加载:
(1)通过一个类的全限定名来获取定义此类的二进制流
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3)在内存中生成一个代表这个类的java.lang.class 对象,作为方法区这个类的各种数据访问入口
注:加载阶段于连接阶段的部分内容是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。
JVM在加载数组的时候加载的仅仅是数组的类型类(例如String[] 加载器只会加载String这个类型类),而数组的创建则由JVM直接完成。
1. JVM为什么只加载数组的类型类
我认为JVM这样做的目的主要是为了节省时间,我们知道数组里面装的都是同一种类型的元素,JVM没必要将一个重复的内容加载多次浪费时间。
2. N维数组怎么加载
如果是N维数组,类加载器会从最外层开始一层一层的递归加载,直到加载到非数组类型为止。
3. 引用类型与基本类型加载起来会不会有区别
其实基本类型早已经在javac阶段装箱成封装对象了,例如int会被装箱成Integer,long装箱成Long等等,所以是没有区别的。来源:https://blog.****.net/qq_23191031/article/details/81838211
2.验证:
目的:为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
过程:
(1)文件格式验证:
1.是否以魔数0xCAFEBABE开头
2.主次版本号是否在当前虚拟机的处理范围内
3.常量池的常量是否有不被支持的常量类型
..................
(2)元数据验证:
1.这个类是否有父类
2.这个类是否继承了不被允许继承的类
3.类中字段,方法是否与父类产生矛盾
(3)字节码验证:
(4)符号引用验证
3.准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都要在方法区进行分配。
注:
进行内存分配的仅包括类变量(即被static修饰的)不包括实例变量,实例变量被分配到java堆中。并且准备阶段过后的初始值为0
4.解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
5.初始化:到了初始化阶段才开始真正执行java程序的代码
初始化阶段是执行类构造器<clint>方法的过程
<clinit>方法:
1.clinit方法是由编译器自动收集类中所有变量的赋值动作和静态语句块的语句合并而成的
2.clinit方法与类的构造函数不同,它不需要显示的调用父类构造器。虚拟机会保证子类的<clinit>方法执行之前,父类<clinit>已经执行
3.父类的静态语句块要优于子类变量的赋值操作
4.clinit方法不是必须的
5.接口中不能使用静态语句块,但接口中仍然有变量初始化的赋值操作,接口也有clinit方法
6.如果多个线程同时去初始化一个类,那么只有一个线程会去执行<clinit>其他的都得等待
有这样一道面试题:
class Singleton{
private static Singleton singleton = new Singleton();
public static int value1;
public static int value2 = 0;
private Singleton(){
value1++;
value2++;
}
public static Singleton getInstance(){
return singleton;
}
}
class Singleton2{
public static int value1;
public static int value2 = 0;
private static Singleton2 singleton2 = new Singleton2();
private Singleton2(){
value1++;
value2++;
}
public static Singleton2 getInstance2(){
return singleton2;
}
}
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("Singleton1 value1:" + singleton.value1);
System.out.println("Singleton1 value2:" + singleton.value2);
Singleton2 singleton2 = Singleton2.getInstance2();
System.out.println("Singleton2 value1:" + singleton2.value1);
System.out.println("Singleton2 value2:" + singleton2.value2);
}
说出运行的结果:
Singleton1 value1 : 1
Singleton1 value2 : 0
Singleton2 value1 : 1
Singleton2 value2 : 1
1 首先执行main中的Singleton singleton = Singleton.getInstance();
2 类的加载:加载类Singleton
3 类的验证
4 类的准备:为静态变量分配内存,设置默认值。这里为singleton(引用类型)设置为null,value1,value2(基本数据类型)设置默认值0
5 类的初始化(按照赋值语句进行修改):
执行private static Singleton singleton = new Singleton();
执行Singleton的构造器:value1++;value2++; 此时value1,value2均等于1
执行
public static int value1;
public static int value2 = 0;
此时value1=1,value2=0
参考:https://blog.****.net/noaman_wgs/article/details/74489549
https://blog.****.net/qq_23191031/article/details/81838211